目录
序列式容器
- 向量(
vector
) 后端可高效增加元素的顺序表。 - 数组(
array
)C++11,定长的顺序表,C 风格数组的简单包装。 - 双端队列(
deque
) 双端都可高效增加元素的顺序表。 - 列表(
list
) 可以沿双向遍历的链表。 - 单向列表(
forward_list
) 只能沿一个方向遍历的链表。
关联式容器
- 集合(
set
) 用以有序地存储 互异 元素的容器。其实现是由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种比较元素大小的谓词进行排列。 - 多重集合(
multiset
) 用以有序地存储元素的容器。允许存在相等的元素。 - 映射(
map
) 由 {键,值} 对组成的集合,以某种比较键大小关系的谓词进行排列。 - 多重映射(
multimap
) 由 {键,值} 对组成的多重集合,亦即允许键有相等情况的映射。
无序(关联式)容器
- 无序(多重)集合(
unordered_set
/unordered_multiset
)C++11,与set
/multiset
的区别在于元素无序,只关心「元素是否存在」,使用哈希实现。 - 无序(多重)映射(
unordered_map
/unordered_multimap
)C++11,与map
/multimap
的区别在于键 (key) 无序,只关心 "键与值的对应关系",使用哈希实现。
容器适配器
容器适配器其实并不是容器。它们不具有容器的某些特点(如:有迭代器、有 clear()
函数……)。
「适配器是使一种事物的行为类似于另外一种事物行为的一种机制」,适配器对容器进行包装,使其表现出另外一种行为。
- 栈(
stack
) 后进先出 (LIFO) 的容器,默认是对双端队列(deque
)的包装。 - 队列(
queue
) 先进先出 (FIFO) 的容器,默认是对双端队列(deque
)的包装。 - 优先队列(
priority_queue
) 元素的次序是由作用于所存储的值对上的某种谓词决定的的一种队列,默认是对向量(vector
)的包装。
共有函数
=
:有赋值运算符以及复制构造函数。
begin()
:返回指向开头元素的迭代器。
end()
:返回指向末尾的下一个元素的迭代器。end()
不指向某个元素,但它是末尾元素的后继。
size()
:返回容器内的元素个数。
max_size()
:返回容器 理论上 能存储的最大元素个数。依容器类型和所存储变量的类型而变。
empty()
:返回容器是否为空。
swap()
:交换两个容器。
clear()
:清空容器。
==
/!=
/<
/>
/<=
/>=
:按 字典序 比较两个容器的大小。(比较元素大小时 map
的每个元素相当于 set<pair<key, value> >
,无序容器不支持 <
/>
/<=
/>=
。)
迭代器
在 STL 中,迭代器(Iterator)用来访问和检查 STL 容器中元素的对象,它的行为模式和指针类似,但是它封装了一些有效性检查,并且提供了统一的访问格式。类似的概念在其他很多高级语言中都存在,如 Python 的 __iter__
函数,C# 的 IEnumerator
。
基础使用
迭代器听起来比较晦涩,其实迭代器本身可以看作一个数据指针。迭代器主要支持两个运算符:自增 (++
) 和解引用(单目 *
运算符),其中自增用来移动迭代器,解引用可以获取或修改它指向的元素。
指向某个 STL 容器 container
中元素的迭代器的类型一般为 container::iterator
。
迭代器可以用来遍历容器,例如,下面两个 for 循环的效果是一样的:
vector<int> data(10);
for (int i = 0; i < data.size(); i++)
cout << data[i] << endl; // 使用下标访问元素
for (vector<int>::iterator iter = data.begin(); iter != data.end(); iter++)
cout << *iter << endl; // 使用迭代器访问元素
// 在C++11后可以使用 auto iter = data.begin() 来简化上述代码
序列式容器
vector
std::vector
是 STL 提供的 内存连续的、可变长度 的数组(亦称列表)数据结构。能够提供线性复杂度的插入和删除,以及常数复杂度的随机访问。
为什么要使用 vector
作为 OIer,对程序效率的追求远比对工程级别的稳定性要高得多,而 vector
由于其对内存的动态处理,时间效率在部分情况下低于静态数组,并且在 OJ 服务器不一定开全优化的情况下更加糟糕。所以在正常存储数据的时候,通常不选择 vector
。下面给出几个 vector
优秀的特性,在需要用到这些特性的情况下,vector
能给我们带来很大的帮助。
vector
可以动态分配内存
很多时候我们不能提前开好那么大的空间(eg:预处理 1~n 中所有数的约数)。尽管我们能知道数据总量在空间允许的级别,但是单份数据还可能非常大,这种时候我们就需要 vector
来把内存占用量控制在合适的范围内。vector
还支持动态扩容,在内存非常紧张的时候这个特性就能派上用场了。
vector
重写了比较运算符及赋值运算符
vector
重载了六个比较运算符,以字典序实现,这使得我们可以方便的判断两个容器是否相等(复杂度与容器大小成线性关系)。例如可以利用 vector<char>
实现字符串比较(当然,还是用 std::string
会更快更方便)。另外 vector
也重载了赋值运算符,使得数组拷贝更加方便。
vector
便利的初始化
由于 vector
重载了 =
运算符,所以我们可以方便的初始化。此外从 C++11 起 vector
还支持 列表初始化,例如 vector<int> data {1, 2, 3};
。
vector
的使用方法
以下介绍常用用法,详细内容 请参见 C++ 文档。
构造函数
用例参见如下代码(假设你已经 using
了 std
命名空间相关类型):
// 1. 创建空vector; 常数复杂度
vector<int> v0;
// 1+. 这句代码可以使得向vector中插入前3个元素时,保证常数时间复杂度
v0.reserve(3);
// 2. 创建一个初始空间为3的vector,其元素的默认值是0; 线性复杂度
vector<int> v1(3);
// 3. 创建一个初始空间为3的vector,其元素的默认值是2; 线性复杂度
vector<int> v2(3, 2);
// 4. 创建一个初始空间为3的vector,其元素的默认值是1,
// 并且使用v2的空间配置器; 线性复杂度
vector<int> v3(3, 1, v2.get_allocator());
// 5. 创建一个v2的拷贝vector v4, 其内容元素和v2一样; 线性复杂度
vector<int> v4(v2);
// 6. 创建一个v4的拷贝vector v5,其内容是{v4[1], v4[2]}; 线性复杂度
vector<int> v5(v4.begin() + 1, v4.begin() + 3);
// 7. 移动v2到新创建的vector v6,不发生拷贝; 常数复杂度; 需要 C++11
vector<int> v6(std::move(v2)); // 或者 v6 = std::move(v2);
元素访问
vector
提供了如下几种方法进行元素访问
-
at()
v.at(pos)
返回容器中下标为pos
的引用。如果数组越界抛出std::out_of_range
类型的异常。 -
operator[]
v[pos]
返回容器中下标为pos
的引用。不执行越界检查。 -
front()
v.front()
返回首元素的引用。 -
back()
v.back()
返回末尾元素的引用。 -
data()
v.data()
返回指向数组第一个元素的指针。
vector<int> v(10);
for (int i = 0; i < 10; i ++ ) v[i] = i;
for (int i = 0; i < 10; i ++ ) cout << v.at(i) << ' ';
puts("");
for (int i = 0; i < 10; i ++ ) cout << v[i] << ' ';
cout << v[100]; //0 不执行越界检查
puts("");
cout << v.front() << ' ' << v.back() << ' ' << v.data() << endl;
迭代器
vector 提供了如下几种 迭代器
-
begin()/cbegin()
返回指向首元素的迭代器,其中
*begin = front
。 -
end()/cend()
返回指向数组尾端占位符的迭代器,注意是没有元素的。
-
rbegin()/crbegin()
返回指向逆向数组的首元素的逆向迭代器,可以理解为正向容器的末元素。
-
rend()/crend()
返回指向逆向数组末元素后一位置的迭代器,对应容器首的前一个位置,没有元素。
以上列出的迭代器中,含有字符 c
的为只读迭代器,你不能通过只读迭代器去修改 vector
中的元素的值。如果一个 vector
本身就是只读的,那么它的一般迭代器和只读迭代器完全等价。只读迭代器自 C++11 开始支持。
长度和容量
vector
有以下几个与容器长度和容量相关的函数。注意,vector
的长度(size)指有效元素数量,而容量(capacity)指其实际分配的内存长度,相关细节请参见后文的实现细节介绍。
与长度相关:
-
empty()
返回一个bool
值,即v.begin() == v.end()
,true
为空,false
为非空。 -
size()
返回容器长度(元素数量),即std::distance(v.begin(), v.end())
。 -
resize()
改变vector
的长度,多退少补。补充元素可以由参数指定。 -
max_size()
返回容器的最大可能长度。与容量相关:
-
reserve()
使得vector
预留一定的内存空间,避免不必要的内存拷贝。 -
capacity()
返回容器的容量,即不发生拷贝的情况下容器的长度上限。 -
shrink_to_fit()
使得vector
的容量与长度一致,多退但不会少。
元素增删及修改
clear()
清除所有元素insert()
支持在某个迭代器位置插入元素、可以插入多个。复杂度与pos
距离末尾长度成线性而非常数的erase()
删除某个迭代器或者区间的元素,返回最后被删除的迭代器。复杂度与insert
一致。push_back()
在末尾插入一个元素,均摊复杂度为 常数,最坏为线性复杂度。pop_back()
删除末尾元素,常数复杂度。swap()
与另一个容器进行交换,此操作是 常数复杂度 而非线性的。
vector<int> ve(10);
for (int i = 0; i < 7; i ++ ) ve[i] = i;
ve.insert(ve.begin() + 2, 7, 8);
for (int i = 0; i < ve.size(); i ++ ) cout << ve[i] << ' ';
puts("");
ve.erase(ve.begin() + 2);
for (int i = 0; i < ve.size(); i ++ ) cout << ve[i] << ' ';
puts("");
ve.push_back(3);
for (int i = 0; i < ve.size(); i ++ ) cout << ve[i] << ' ';
puts("");
ve.pop_back();
for (int i = 0; i < ve.size(); i ++ ) cout << ve[i] << ' ';
puts("");
array
(C++11)
std::array
是 STL 提供的 内存连续的、固定长度 的数组数据结构。其本质是对原生数组的直接封装。
为什么要用 array
array
实际上是 STL 对数组的封装。它相比 vector
牺牲了动态扩容的特性,但是换来了与原生数组几乎一致的性能(在开满优化的前提下)。因此如果能使用 C++11 特性的情况下,能够使用原生数组的地方几乎都可以直接把定长数组都换成 array
,而动态分配的数组可以替换为 vector
。
成员函数
隐式定义的成员函数
函数 | 作用 |
---|---|
operator= | 以来自另一 array 的每个元素重写 array 的对应元素 |
元素访问
函数 | 作用 |
---|---|
at | 访问指定的元素,同时进行越界检查 |
operator[] | 访问指定的元素,不 进行越界检查 |
front | 访问第一个元素 |
back | 访问最后一个元素 |
data | 返回指向内存中数组第一个元素的指针 |
at
若遇 pos >= size()
的情况会抛出 std::out_of_range
。
容量
函数 | 作用 |
---|---|
empty | 检查容器是否为空 |
size | 返回容纳的元素数 |
max_size | 返回可容纳的最大元素数 |
由于每个 array
都是固定大小容器,size()
返回的值等于 max_size()
返回的值。
操作
函数 | 作用 |
---|---|
fill | 以指定值填充容器 |
swap | 交换内容 |
注意,交换两个 array
是 的,而非与常规 STL 容器一样为 。
非成员函数
函数 | 作用 |
---|---|
operator== 等 | 按照字典序比较 array 中的值 |
std::get | 访问 array 的一个元素 |
std::swap | 特化的 std::swap 算法 |
array<int, 10> ar;
for (int i = 0; i < ar.size(); i ++ ) ar[i] = i;
for (int i = 0; i < ar.size(); i ++ ) cout << ar.at(i) << ' ';
puts("");
for (int i = 0; i < ar.size(); i ++ ) cout << ar[i] << ' ';
puts("");
cout << ar[100]; // 0
puts("");
cout << ar.front() << ' ' << ar.back() << ' ' << ar.data() << endl;
cout << ar.empty() << ' ' << ar.size() << ' ' << ar.max_size() << endl;
ar.fill(1);
for (int i = 0; i < ar.size(); i ++ ) cout << ar[i] << ' ';
puts("");
deque
std::deque
是 STL 提供的 双端队列 数据结构。能够提供线性复杂度的插入和删除,以及常数复杂度的随机访问。
deque
的使用方法
以下介绍常用用法,详细内容 请参见 C++ 文档。deque
的迭代器函数与 vector
相同,因此不作详细介绍。
// 1. 定义一个int类型的空双端队列 de0
deque<int> de0;
// 2. 定义一个int类型的双端队列 de1,并设置初始大小为10; 线性复杂度
deque<int> de1(10);
// 3. 定义一个int类型的双端队列 de2,并初始化为10个1; 线性复杂度
deque<int> de2(10, 1);
// 4. 复制已有的双端队列 de1; 线性复杂度
deque<int> de3(de1);
// 5. 创建一个de2的拷贝deque de4,其内容是de4[0]至de4[2]; 线性复杂度
deque<int> de(v2.begin(), v2.begin() + 3);
// 6. 移动de2到新创建的deque de5,不发生拷贝; 常数复杂度; 需要 C++11
deque<int> de5(std::move(de2));
元素访问
与 vector
一致,但无法访问底层内存。其高效的元素访问速度可参考实现细节部分。
at()
返回容器中指定位置元素的引用,执行越界检查,常数复杂度。operator[]
返回容器中指定位置元素的引用。不执行越界检查,常数复杂度。front()
返回首元素的引用。back()
返回末尾元素的引用。
deque<int> de(10);
for (int i = 0; i < 10; i ++ ) de[i] = i;
for (int i = 0; i < de.size(); i ++ ) cout << de.at(i) << ' ';
puts("");
for (int i = 0; i < de.size(); i ++ ) cout << de[i] << ' ';
puts("");
cout << de.front() << ' ' << de.back() << endl;
迭代器
与 vector
一致。
长度
与 vector
一致,但是没有 reserve()
和 capacity()
函数。(仍然有 shrink_to_fit()
函数)
元素增删及修改
与 vector
一致,并额外有向队列头部增加元素的函数。
clear()
清除所有元素insert()
支持在某个迭代器位置插入元素、可以插入多个。复杂度与pos
与两端距离较小者成线性。erase()
删除某个迭代器或者区间的元素,返回最后被删除的迭代器。复杂度与insert
一致。push_front()
在头部插入一个元素,常数复杂度。pop_front()
删除头部元素,常数复杂度。push_back()
在末尾插入一个元素,常数复杂度。pop_back()
删除末尾元素,常数复杂度。swap()
与另一个容器进行交换,此操作是 常数复杂度 而非线性的。
de.insert(de.end(), 6);
for (int i = 0; i < de.size(); i ++ ) cout << de.at(i) << ' ';
puts("");
de.insert(de.end(), 6, 6);
for (int i = 0; i < de.size(); i ++ ) cout << de.at(i) << ' ';
puts("");
de.erase(de.begin() + 4);
for (int i = 0; i < de.size(); i ++ ) cout << de.at(i) << ' ';
puts("");
de.erase(de.begin(), de.begin() + 4);
for (int i = 0; i < de.size(); i ++ ) cout << de.at(i) << ' ';
puts("");
de.push_front(100);
de.push_back(100);
for (int i = 0; i < de.size(); i ++ ) cout << de.at(i) << ' ';
puts("");
de.pop_front();
de.pop_back();
for (int i = 0; i < de.size(); i ++ ) cout << de.at(i) << ' ';
puts("");
deque
的实现细节
deque
通常的底层实现是多个不连续的缓冲区,而缓冲区中的内存是连续的。而每个缓冲区还会记录首指针和尾指针,用来标记有效数据的区间。当一个缓冲区填满之后便会在之前或者之后分配新的缓冲区来存储更多的数据。更详细的说明可以参考 STL 源码剖析——deque 的实现原理和使用方法详解。
list
std::list
是 STL 提供的 双向链表 数据结构。能够提供线性复杂度的随机访问,以及常数复杂度的插入和删除。
list
的使用方法
list
的使用方法与 deque
基本相同,但是增删操作和访问的复杂度不同。详细内容 请参见 C++ 文档。list
的迭代器、长度、元素增删及修改相关的函数与 deque
相同,因此不作详细介绍。
元素访问
由于 list
的实现是链表,因此它不提供随机访问的接口。若需要访问中间元素,则需要使用迭代器。
front()
返回首元素的引用。back()
返回末尾元素的引用。
操作
list
类型还提供了一些针对其特性实现的 STL 算法函数。由于这些算法需要 随机访问迭代器,因此 list
提供了特别的实现以便于使用。这些算法有 splice()
、remove()
、sort()
、unique()
、merge()
等。
forward_list
(C++11)
std::forward_list
是 STL 提供的 单向链表 数据结构,相比于 std::list
减小了空间开销。
forward_list
的使用方法
forward_list
的使用方法与 list
几乎一致,但是迭代器只有单向的,因此其具体用法不作详细介绍。详细内容 请参见 C++ 文档
关联式容器
set
set
是关联容器,含有键值类型对象的已排序集,搜索、移除和插入拥有对数复杂度。set
内部通常采用红黑树实现。平衡二叉树的特性使得 set
非常适合处理需要同时兼顾查找、插入与删除的情况。
和数学中的集合相似,set
中不会出现值相同的元素。如果需要有相同元素的集合,需要使用 multiset
。multiset
的使用方法与 set
的使用方法基本相同。
插入与删除操作
insert(x)
当容器中没有等价元素的时候,将元素 x 插入到set
中。erase(x)
删除值为 x 的 所有 元素,返回删除元素的个数。erase(pos)
删除迭代器为 pos 的元素,要求迭代器必须合法。erase(first,last)
删除迭代器在 范围内的所有元素。clear()
清空set
。
set<int> s;
s.insert(5);
for (auto it = s.begin(); it != s.end(); it ++ ) cout << *it << ' ';
puts("");
for (int i = 0; i < 10; i ++ ) s.insert(i);
for (auto it = s.begin(); it != s.end(); it ++ ) cout << *it << ' ';
puts("");
s.erase(5); //删除所有值为5
for (auto it = s.begin(); it != s.end(); it ++ ) cout << *it << ' ';
puts("");
auto it1 = s.begin();
auto it2 = s.begin();
advance(it1, 2);
advance(it2, 5);
s.erase(it1, it2); //前闭后开
for (auto it = s.begin(); it != s.end(); it ++ ) cout << *it << ' ';
puts("");
insert 函数的返回值
insert 函数的返回值类型为 pair<iterator, bool>
,其中 iterator 是一个指向所插入元素(或者是指向等于所插入值的原本就在容器中的元素)的迭代器,而 bool 则代表元素是否插入成功,由于 set
中的元素具有唯一性质,所以如果在 set
中已有等值元素,则插入会失败,返回 false,否则插入成功,返回 true;map
中的 insert 也是如此。
迭代器
set
提供了以下几种迭代器:
begin()/cbegin()
返回指向首元素的迭代器,其中*begin = front
。end()/cend()
返回指向数组尾端占位符的迭代器,注意是没有元素的。rbegin()/crbegin()
返回指向逆向数组的首元素的逆向迭代器,可以理解为正向容器的末元素。rend()/crend()
返回指向逆向数组末元素后一位置的迭代器,对应容器首的前一个位置,没有元素。
以上列出的迭代器中,含有字符 c
的为只读迭代器,你不能通过只读迭代器去修改 set
中的元素的值。如果一个 set
本身就是只读的,那么它的一般迭代器和只读迭代器完全等价。只读迭代器自 C++11 开始支持。
查找操作
count(x)
返回set
内键为 x 的元素数量。find(x)
在set
内存在键为 x 的元素时会返回该元素的迭代器,否则返回end()
。lower_bound(x)
返回指向首个不小于给定键的元素的迭代器。如果不存在这样的元素,返回end()
。upper_bound(x)
返回指向首个大于给定键的元素的迭代器。如果不存在这样的元素,返回end()
。empty()
返回容器是否为空。size()
返回容器内元素个数。
cout << s.count(1) << endl;
auto iter = s.find(0);
if (iter != s.end()) cout << *iter << endl;
else cout << "Not found" << endl;
auto iter1 = s.lower_bound(5);
auto iter2 = s.upper_bound(5);
cout << *iter1 << ' ' << *iter2 << endl;
lower_bound
和 upper_bound
的时间复杂度
set
自带的 lower_bound
和 upper_bound
的时间复杂度为 。
但使用 algorithm
库中的 lower_bound
和 upper_bound
函数对 set
中的元素进行查询,时间复杂度为 。
nth_element
的时间复杂度
set
没有提供自带的 nth_element
。使用 algorithm
库中的 nth_element
查找第 大的元素时间复杂度为 。
如果需要实现平衡二叉树所具备的 查找第 大元素的功能,需要自己手写平衡二叉树或权值线段树,或者选择使用 pb_ds 库中的平衡二叉树。
使用样例
set
在贪心中的使用
在贪心算法中经常会需要出现类似 找出并删除最小的大于等于某个值的元素。这种操作能轻松地通过 set
来完成。
map
map
是有序键值对容器,它的元素的键是唯一的。搜索、移除和插入操作拥有对数复杂度。map
通常实现为红黑树。
你可能需要存储一些键值对,例如存储学生姓名对应的分数:Tom 0
,Bob 100
,Alan 100
。但是由于数组下标只能为非负整数,所以无法用姓名作为下标来存储,这个时候最简单的办法就是使用 STL 中的 map
了!
map
重载了 operator[]
,可以用任意定义了 operator <
的类型作为下标(在 map
中叫做 key
,也就是索引):
1 | |
其中,Key
是键的类型,T
是值的类型,下面是使用 map
的实例:
1 | |
map
中不会存在键相同的元素,multimap
中允许多个元素拥有同一键。multimap
的使用方法与 map
的使用方法基本相同。
插入与删除操作
- 可以直接通过下标访问来进行查询或插入操作。例如
mp["Alan"]=100
。 - 通过向
map
中插入一个类型为pair<Key, T>
的值可以达到插入元素的目的,例如mp.insert(pair<string,int>("Alan",100));
; erase(key)
函数会删除键为key
的 所有 元素。返回值为删除元素的数量。erase(pos)
: 删除迭代器为 pos 的元素,要求迭代器必须合法。erase(first,last)
: 删除迭代器在 范围内的所有元素。clear()
函数会清空整个容器。
map<string, int> mp;
mp["x"] = 100;
mp.insert(pair<string, int>("xx", 1000));
for (auto iter : mp) cout << iter.first << ' ' << iter.second << endl;
cout << mp.erase("x") << endl;
for (auto iter : mp) cout << iter.first << ' ' << iter.second << endl;
mp["a"] = 6;
mp["b"] = 10;
mp["cd"] = 87;
mp["e"] = 1;
for (auto iter : mp) cout << iter.first << ' ' << iter.second << endl;
auto er1 = mp.begin();
auto er2 = mp.begin();
advance(er2, 2);
mp.erase(er1, er2); //前闭后开
for (auto iter : mp) cout << iter.first << ' ' << iter.second << endl;
下标访问中的注意事项
在利用下标访问 map
中的某个元素时,如果 map
中不存在相应键的元素,会自动在 map
中插入一个新元素,并将其值设置为默认值(对于整数,值为零;对于有默认构造函数的类型,会调用默认构造函数进行初始化)。
当下标访问操作过于频繁时,容器中会出现大量无意义元素,影响 map
的效率。因此一般情况下推荐使用 find()
函数来寻找特定键的元素。
查询操作
count(x)
: 返回容器内键为 x 的元素数量。复杂度为 (关于容器大小对数复杂度,加上匹配个数)。find(x)
: 若容器内存在键为 x 的元素,会返回该元素的迭代器;否则返回end()
。lower_bound(x)
: 返回指向首个不小于给定键的元素的迭代器。upper_bound(x)
: 返回指向首个大于给定键的元素的迭代器。若容器内所有元素均小于或等于给定键,返回end()
。empty()
: 返回容器是否为空。size()
: 返回容器内元素个数。
cout << mp.count("e") << endl;
auto er = mp.find("e");
if (er != mp.end()) cout << er->first << ' ' << er->second << endl;
使用样例
map
用于存储复杂状态
在搜索中,我们有时需要存储一些较为复杂的状态(如坐标,无法离散化的数值,字符串等)以及与之有关的答案(如到达此状态的最小步数)。map
可以用来实现此功能。其中的键是状态,而值是与之相关的答案。下面的示例展示了如何使用 map
存储以 string
表示的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
遍历容器
可以利用迭代器来遍历关联式容器的所有元素。
1 2 3 | |
需要注意的是,对 map
的迭代器解引用后,得到的是类型为 pair<Key, T>
的键值对。
在 C++11 中,使用范围 for 循环会让代码简洁很多:
1 2 | |
对于任意关联式容器,使用迭代器遍历容器的时间复杂度均为 。
自定义比较方式
set
在默认情况下的比较函数为 <
(如果是非内置类型需要 重载 < 运算符)。然而在某些特殊情况下,我们希望能自定义 set
内部的比较方式。
这时候可以通过传入自定义比较器来解决问题。
具体来说,我们需要定义一个类,并在这个类中 重载 () 运算符。
例如,我们想要维护一个存储整数,且较大值靠前的 set
,可以这样实现:
1 2 3 4 5 | |
对于其他关联式容器,可以用类似的方式实现自定义比较,这里不再赘述。
容器适配器
栈
STL 栈 (std::stack
) 是一种后进先出 (Last In, First Out) 的容器适配器,仅支持查询或删除最后一个加入的元素(栈顶元素),不支持随机访问,且为了保证数据的严格有序性,不支持迭代器。
头文件
1 | |
定义
1 2 3 | |
成员函数
以下所有函数均为常数复杂度
top()
访问栈顶元素(如果栈为空,此处会出错)push(x)
向栈中插入元素 xpop()
删除栈顶元素size()
查询容器中的元素数量empty()
询问容器是否为空
简单示例
1 2 3 4 5 6 7 8 9 | |
队列
STL 队列 (std::queue
) 是一种先进先出 (First In, First Out) 的容器适配器,仅支持查询或删除第一个加入的元素(队首元素),不支持随机访问,且为了保证数据的严格有序性,不支持迭代器。
头文件
1 | |
定义
1 2 3 4 | |
成员函数
以下所有函数均为常数复杂度
front()
访问队首元素(如果队列为空,此处会出错)push(x)
向队列中插入元素 xpop()
删除队首元素size()
查询容器中的元素数量empty()
询问容器是否为空
简单示例
1 2 3 4 5 6 7 8 9 | |
优先队列
头文件
1 | |
定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
成员函数
以下所有函数均为常数复杂度
top()
访问堆顶元素(此时优先队列不能为空)empty()
询问容器是否为空size()
查询容器中的元素数量
以下所有函数均为对数复杂度
push(x)
插入元素,并对底层容器排序pop()
删除堆顶元素(此时优先队列不能为空)
简单示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |