10.vector、set和map

文章介绍了C++中的动态数组vector,包括其定义、基本操作如插入、访问和修改元素、获取长度、删除元素等。接着讲解了集合set的概念、定义、插入元素、查找、删除、遍历等操作。最后,讨论了映射map的特性,如何插入映射对、访问和修改值、遍历映射。文章强调了STL在C++中简化编程的作用,并提供了相关练习题。

一、vector

1.简介

有些时候想开一个数组,但是却不知道应该开多大长度的数组合适,因为我们需要用到的数组可能会根据情况变动。这时候我们就需要用到动态数组。

所谓动态数组,也就是不定长数组,数组的长度是可以根据我们的需要动态改变的。动态数组的实现也不难,但是在 C++ 里面有已经写好的标准模板库 STL,实现了集合、映射表、栈、队列等数据结构和排序、查找等算法。我们可以很方便地调用标准库来减少我们的代码量。

C++ 中动态数组写作 vector ,C 语言中没有标准模板库,这也是为什么参加比赛推荐用 C++ 而不用 C。

2.定义

#include <vector>
using namespace std;
int main()
{
    return 0;
}

使用 STL 的动态数组需要首先在代码的开头引入这个头文件,并不要忘记 using namespace std;

vector<T> vec;

T T T 表示我们所定义的数组存储数据的类型。它可以是int,double 这样的基本数据类型,可以是 typedef 之后的代词,也可以是自定义的结构体类型,甚至可以是其他的 STL 类型。

typedef long long ll;
vector<int> a;
vector<double> b;
vector<ll> c;
vector<node> d;
vector<vector<ll> >e;

一开始定义好的动态数组的长度为 0 0 0,代表一个空的数组。

3.基本操作

(1)插入元素到尾部 push_back()

vector<int> vec; //{}
vec.push_back(1);//{1}
vec.push_back(2);//{1, 2}
vec.push_back(3);//{1, 2, 3}

(2)访问以及修改某一元素的值

cout < <vec[2] << endl;//3
vec[2] = 10;
cout << vec[2] << endl;//10

注意动态数组默认的下标是从 0 0 0 开始的。除此以外,所有的操作和普通的数组是一样的。但是需要注意的是,必须先 push_back(),确定 vec[2] 存在的情况下,才可以进行修改并访问,否则访问是无效的。

(3)获取数组长度 size()

cout << vec.size() << endl;//3
for(int i = 0; i < vec.size(); i++)
    cout << vec[i] << endl;
//{1,2,10}

这里的 size() 也可以用 length() 代替,它们这个使用条件下完全相同

(4)删除末尾元素

vec.pop_back();//{1, 2}
vec.pop_back();//{1}

(5)清空数组

vec.push_back(10);//{1, 10}
vec.push_back(20);//{1, 10, 20}
vec.clear();      //{}

但需要注意的是,clear()只清空了数组,但没有释放掉动态数组的空间

vector<int> x;
{
    vector<int> New;
    x.swap(New);
}

如果要释放掉 vector 的空间,那么最好的方法是创建一个空的数组,并将其替换。

(6)初始化

int n = 10;
vector<int> vec1(n,1);

这里给数组预先分配了长度为 n n n 的空间,并将这 n n n 个元素全部设为初始值 1 1 1

二、set

1.简介

集合是一个特殊的数据结构,在集合中不会出现同样的元素。集合中的元素是按一定的顺序排列的,默认按照从小到大的顺序。我们可以通过迭代器来顺序访问每一个元素。

如果想用可重复的集合,那么可以使用 multiset,它们的实现方式都是红黑树,支持的函数也都差不多,这里不再赘述。

2.定义

set<T> s;

T T T 表示我们所定义的数组存储数据的类型。它可以是int,double 这样的基本数据类型,可以是 typedef 之后的代词,也可以是自定义的结构体类型,甚至可以是其他的 STL 类型。

#include <set>
using namespace std;

typedef long long ll;
struct node
{
    ll x,y;
    bool operator < (const node &a) const
    {
        if(x == a.x)
            return y < a.y;
        return x < a.x;
    }
};
set<int> a;
set<double> b;
set<ll> c;
set<node> d;
set<vector<ll> >e;

但需要注意的是,如果定义的集合是建立在结构体上的,因为涉及到set的有序性,所以必须重载运算符。

一开始定义好的集合的长度为 0 0 0,代表一个空的集合。

3.基本操作

(1)插入元素insert()

set<int> s; //{}
s.insert(2);//{2}
s.insert(1);//{1, 2}
s.insert(3);//{1, 2, 3}
s.insert(3);//{1, 2, 3}
  • set 中插入元素时,会自动排序。
  • set 中插入已有元素时,插入不会产生任何效果。

(2)查找某一元素count()

由于 set 是有序的,所以可以很快速的查询出某一个元素是否在集合中。

//{1,2,3}
if(s.count(5))
    cout << "5 is in the set." << endl;
else
    cout << "5 is not in the set." << endl;

(3)获取集合大小size()

//{1, 2, 3}
cout << s.size() << endl;//3

(4)删除某一个元素

s.erase(10);//{1,2}
s.earse(8);//{1,2}

删除集合中的某一个元素,如果此元素不在集合中,那么删除不会产生任何效果。

(5)查找某一元素 find()

//{1,2,10,20}
if(s.find(10) != s.end())
    cout << "10 is in the set." << endl;

find() 函数可以查找一个元素并返回其对应的迭代器,如果集合中没有该元素,那么就会返回 end()

(6)清空集合clear()

//{1, 2}
s.insert(10);//{1, 2, 10}
s.insert(20);//{1, 2, 10, 20}
s.clear();   //{}

但需要注意的是,与 vector 不同的是,setclear() 不仅删除了元素,还清空了内存。

(7)遍历

//{1, 2, 10, 20}
for(set<int>::iterator it = s.begin(); it != s.end(); it++)
    cout << (*it) << " ";
//1 2 10 20

因为集合使用的不是连续空间,而是使用了迭代器,所以我们需要使用迭代器 iterator 来遍历集合。

注意 s.begin()s.end() 并不是整数,而是迭代器类型,所以 it!=s.end() 不能改成 it<s.end()

遍历的结果是按照定义的顺序排列的。

(8)二分查找

set<int>::iterator it1 = st.upper_bound(x);//第一个小于x的数
set<int>::iterator it2 = st.lower_bound(x);//第一个小于等于x的数

标准库里有针对数组或者 vector 等容器的二分查询函数 lower_bound()upper_bound()。虽然set仍然可以使用,但是因为 set 不支持下标访问,查询速度可能会退化到 O ( n ) O(n) O(n)set 里内置了相关的查询函数,建议使用这些函数。

三、map

1.简介

两个非空集合 A A A B B B 间存在着对应关系 f f f,而且对于 A A A 中的每一个元素 a a a B B B 中总有唯一元素 b b b 与它对应,就将这种对应为从 A A A B B B 的映射,记作 f : A → B f:A\rightarrow B f:AB。其中, b b b 称为元素 a a a 在映射 f f f 下的,记作: b = f ( a ) b=f(a) b=f(a) a a a 称为 b b b 关于映射 f f f原像。集合 A A A 中所有元素的像的集合称为映射 f f f的值域,记作 f ( A ) f(A) f(A)

比如集合 { ′ A ′ , ′ B ′ , ′ C ′ } \{'A','B','C'\} {A,B,C} { 1 , 2 , 3 } \{1,2,3\} {1,2,3} 可以构成如下映射:

在这里插入图片描述

2.定义

map<T1, T2> mp;

T 1 , T 2 T1,T2 T1,T2 表示我们所定义的数组存储数据的类型,它们可以是相同的也可以是不同的。它可以是 int,double 这样的基本数据类型,可以是 typedef 之后的代词,也可以是自定义的结构体类型,甚至可以是其他的 STL 类型。

#include <map>
using namespace std;

typedef long long ll;
map<int, int> a;
map<double, int> b;
map<ll, ll> c;
map<node> d;
map<vector<ll> >e;

一开始定义好的动态数组的长度为 0 0 0,代表一个空的数组。

3.基本操作

(1)插入一对映射insert()

map<string, int> mp;                //{}
mp.insert(pair<string, int>("A", 3));//{(A, 3)}
mp.insert(pair<string, int>("B", 1));//{(A, 3),(B, 1)}
mp.insert(pair<string, int>("C", 2));//{(A, 3),(B, 1),(C, 2)}
mp.insert(make_pair("D", 4));       //{(A, 3),(B, 1),(C, 2),(D, 4)}
mp.insert(make_pair("A", 4));       //{(A, 3),(B, 1),(C, 2),(D, 4)}

因为映射描述的是一种关系,涉及到两个元素,所以这里用 pair 这一类型类帮助我们插入。pair 表示一个二元组,它的类型被 <> 定义。可以用如上两种方式创造一个 pair 变量。pair 有两个域 firstsecond,访问方式如下:

pair<string, int> temp = make_pair("A", 4);
cout << temp.first << " " << temp.second << endl;

如果插入的原像已有对应的,那么这样的插入就是无效的,并不会替代原来的值。

(2)访问以及修改某一对映射的值

//{(A, 3), (B, 1), (C, 2), (D, 4)}
cout << mp["B"] << endl;//1
mp["B"] = 10;
//{(A, 3), (B, 10), (C, 2), (D, 4)}
cout << mp["B"] << endl;//10
mp["E"] = 2;
//{(A, 3), (B, 10), (C, 2), (D, 4), (E, 2)}

访问map和访问数组几乎一致,修改的方式也是一样的,直接赋值即可。如果访问的原像并没有对应的,那么它就会创建一个新的映射。

(3)获取映射对个数 size()

cout << mp.size() << endl;//5

注意一个可以有很多个原像,但一个原像不可以有多个。所以注意这里是映射对的数量,而不是的数量。

(4)查询某一个原像 count()

//{(A, 3), (B, 10), (C, 2), (D, 4), (E, 2)}
if(mp.count("A"))
    cout << mp["A"] << endl;

如果一个原像没有对应的,那么直接访问会报错。所以要先用 count() 查询是否存在之后再访问。

(5)遍历映射

for(map<string, int>::iterator it = mp.begin(); it != mp.end(); it++)
    cout << (*it).first << " " << (*it).second << endl;

因为映射使用的不是连续空间,而是使用了迭代器,所以我们需要使用迭代器iterator来遍历映射。

注意 s.begin() s.end() 并不是整数,而是迭代器类型,所以 it != s.end() 不能改成 it < s.end()

遍历的结果是按照定义的顺序排列的。

(6)清空映射 clear()

mp.clear();//{}

map clear() 不仅删除了元素,还清空了内存。

四、作业

1.vector

P5732 【深基5.习7】杨辉三角

P3613 【深基15.例2】寄包柜

2.set

P1059 [NOIP2006 普及组] 明明的随机数

P1152 欢乐的跳

P5143 攀爬者

3.map

P1102 A-B 数对

P6402 [COCI2014-2015#2] UTRKA

P5266 【深基17.例6】学籍管理

VectorsetmapC++中常用的数据结构,它们在存储方式、元素特性适用场景等方面存在明显区别: - **存储方式**: - **Vector**:基于数组实现,拥有一段连续的内存空间,元素按顺序依次存放[^1][^2]。 - **Set**:基于红黑树(一种自平衡二叉搜索树)实现,元素存储在树结构中,并非连续存储[^3]。 - **Map**:同样基于红黑树实现,以键值对的形式存储数据,键值通过树结构关联起来,存储也不连续[^1]。 - **元素特性**: - **Vector**:可以存储重复元素,并且元素的插入顺序就是存储顺序,支持通过下标随机访问元素,越界访问时,使用`at()`方法会抛出异常,比`[]`更安全[^2]。 - **Set**:只能存储唯一的元素,插入重复元素时会被忽略,内部元素会根据元素的值自动排序,在将自定义类型应用于set时,需要定义“小于”运算符,不支持下标访问[^1][^3]。 - **Map**:键(key)必须是唯一的,不同的键可以对应相同的值,元素会根据键自动排序,通过键来访问对应的值,也不支持下标访问[^1]。 - **适用场景**: - **Vector**:适用于需要频繁随机访问元素,且元素插入删除操作主要在容器末尾进行的场景,例如动态数组的场景[^2]。 - **Set**:常用于数据去重需要对元素进行排序的场景,例如统计不同元素的个数[^3]。 - **Map**:适用于需要通过键快速查找对应值的场景,例如字典、映射关系的存储等。 ### 代码示例 ```cpp #include <iostream> #include <vector> #include <set> #include <map> int main() { // Vector示例 std::vector<int> vec; vec.push_back(1); vec.push_back(2); vec.push_back(2); // 可以存储重复元素 std::cout << "Vector elements: "; for (int i = 0; i < vec.size(); ++i) { std::cout << vec.at(i) << " "; // 通过下标访问元素 } std::cout << std::endl; // Set示例 std::set<int> s; s.insert(2); s.insert(3); s.insert(1); s.insert(1); // 重复元素会被忽略 std::cout << "Set elements: "; for (auto it = s.begin(); it != s.end(); ++it) { std::cout << *it << " "; // 元素自动排序 } std::cout << std::endl; // Map示例 std::map<std::string, int> m; m["apple"] = 1; m["banana"] = 2; m["apple"] = 3; // 键唯一,会覆盖之前的值 std::cout << "Map elements: "; for (auto it = m.begin(); it != m.end(); ++it) { std::cout << it->first << ": " << it->second << " "; // 根据键排序 } std::cout << std::endl; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值