C++ STL

目录

序列式容器

关联式容器

无序(关联式)容器

容器适配器

共有函数

迭代器

基础使用

序列式容器

vector

为什么要使用 vector

vector 的使用方法

元素访问

迭代器

长度和容量

元素增删及修改

array(C++11)

为什么要用 array

成员函数

操作

非成员函数

deque

deque 的使用方法

deque 的实现细节

list

list 的使用方法

forward_list(C++11)

forward_list 的使用方法

关联式容器

set

插入与删除操作

迭代器

查找操作

使用样例

map

插入与删除操作

查询操作

使用样例

遍历容器

自定义比较方式

容器适配器

头文件

定义

成员函数

简单示例

队列

头文件

定义

成员函数

简单示例

优先队列

头文件

定义

成员函数

简单示例


序列式容器

  • 向量(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 提供了如下几种方法进行元素访问

  1. at()

    v.at(pos) 返回容器中下标为 pos 的引用。如果数组越界抛出 std::out_of_range 类型的异常。

  2. operator[]

    v[pos] 返回容器中下标为 pos 的引用。不执行越界检查。

  3. front()

    v.front() 返回首元素的引用。

  4. back()

    v.back() 返回末尾元素的引用。

  5. 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 提供了如下几种 迭代器

  1. begin()/cbegin()

    返回指向首元素的迭代器,其中 *begin = front

  2. end()/cend()

    返回指向数组尾端占位符的迭代器,注意是没有元素的。

  3. rbegin()/crbegin()

    返回指向逆向数组的首元素的逆向迭代器,可以理解为正向容器的末元素。

  4. 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 中不会出现值相同的元素。如果需要有相同元素的集合,需要使用 multisetmultiset 的使用方法与 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 提供了以下几种迭代器:

  1. begin()/cbegin()
    返回指向首元素的迭代器,其中 *begin = front
  2. end()/cend()
    返回指向数组尾端占位符的迭代器,注意是没有元素的。
  3. rbegin()/crbegin()
    返回指向逆向数组的首元素的逆向迭代器,可以理解为正向容器的末元素。
  4. 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 0Bob 100Alan 100。但是由于数组下标只能为非负整数,所以无法用姓名作为下标来存储,这个时候最简单的办法就是使用 STL 中的 map 了!

map 重载了 operator[],可以用任意定义了 operator < 的类型作为下标(在 map 中叫做 key,也就是索引):

1
map<Key, T> yourMap;

其中,Key 是键的类型,T 是值的类型,下面是使用 map 的实例:

1
map<string, int> mp;

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
// 存储状态与对应的答案
map<string, int> record;

// 新搜索到的状态与对应答案
string status;
int ans;
// 查找对应的状态是否出现过
map<string, int>::iterator it = record.find(status);
if (it == record.end()) {
  // 尚未搜索过该状态,将其加入状态记录中
  record[status] = ans;
  // 进行相应操作……
} else {
  // 已经搜索过该状态,进行相应操作……
}

遍历容器

可以利用迭代器来遍历关联式容器的所有元素。

1
2
3
set<int> s;
typedef set<int>::iterator si;
for (si it = s.begin(); it != s.end(); it++) cout << *it << endl;

需要注意的是,对 map 的迭代器解引用后,得到的是类型为 pair<Key, T> 的键值对。

在 C++11 中,使用范围 for 循环会让代码简洁很多:

1
2
set<int> s;
for (auto x : s) cout << x << endl;

对于任意关联式容器,使用迭代器遍历容器的时间复杂度均为 

自定义比较方式

set 在默认情况下的比较函数为 <(如果是非内置类型需要 重载 < 运算符)。然而在某些特殊情况下,我们希望能自定义 set 内部的比较方式。

这时候可以通过传入自定义比较器来解决问题。

具体来说,我们需要定义一个类,并在这个类中 重载 () 运算符

例如,我们想要维护一个存储整数,且较大值靠前的 set,可以这样实现:

1
2
3
4
5
struct cmp {
  bool operator()(int a, int b) { return a > b; }
};

set<int, cmp> s;

对于其他关联式容器,可以用类似的方式实现自定义比较,这里不再赘述。

容器适配器

STL 栈 (std::stack) 是一种后进先出 (Last In, First Out) 的容器适配器,仅支持查询或删除最后一个加入的元素(栈顶元素),不支持随机访问,且为了保证数据的严格有序性,不支持迭代器。

头文件

1
#include <stack>

定义

1
2
3
std::stack<TypeName> s;  // 使用默认底层容器 deque,数据类型为 TypeName
std::stack<TypeName, Container> s;  // 使用 Container 作为底层容器
std::stack<TypeName> s2(s1);        // 将 s1 复制一份用于构造 s2

成员函数

以下所有函数均为常数复杂度

  • top() 访问栈顶元素(如果栈为空,此处会出错)
  • push(x) 向栈中插入元素 x
  • pop() 删除栈顶元素
  • size() 查询容器中的元素数量
  • empty() 询问容器是否为空

简单示例

1
2
3
4
5
6
7
8
9
std::stack<int> s1;
s1.push(2);
s1.push(1);
std::stack<int> s2(s1);
s1.pop();
std::cout << s1.size() << " " << s2.size() << std::endl;  // 1 2
std::cout << s1.top() << " " << s2.top() << std::endl;    // 2 1
s1.pop();
std::cout << s1.empty() << " " << s2.empty() << std::endl;  // 1 0

队列

STL 队列 (std::queue) 是一种先进先出 (First In, First Out) 的容器适配器,仅支持查询或删除第一个加入的元素(队首元素),不支持随机访问,且为了保证数据的严格有序性,不支持迭代器。

头文件

1
#include <queue>

定义

1
2
3
4
std::queue<TypeName> q;  // 使用默认底层容器 deque,数据类型为 TypeName
std::queue<TypeName, Container> q;  // 使用 Container 作为底层容器

std::queue<TypeName> q2(q1);  // 将 s1 复制一份用于构造 q2

成员函数

以下所有函数均为常数复杂度

  • front() 访问队首元素(如果队列为空,此处会出错)
  • push(x) 向队列中插入元素 x
  • pop() 删除队首元素
  • size() 查询容器中的元素数量
  • empty() 询问容器是否为空

简单示例

1
2
3
4
5
6
7
8
9
std::queue<int> q1;
q1.push(2);
q1.push(1);
std::queue<int> q2(q1);
q1.pop();
std::cout << q1.size() << " " << q2.size() << std::endl;    // 1 2
std::cout << q1.front() << " " << q2.front() << std::endl;  // 1 2
q1.pop();
std::cout << q1.empty() << " " << q2.empty() << std::endl;  // 1 0

优先队列

头文件

1
#include <queue>

定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
std::priority_queue<TypeName> q;             // 数据类型为 TypeName
std::priority_queue<TypeName, Container> q;  // 使用 Container 作为底层容器
std::priority_queue<TypeName, Container, Compare> q;
// 使用 Container 作为底层容器,使用 Compare 作为比较类型

// 默认使用底层容器 vector
// 比较类型 less<TypeName>(此时为它的 top() 返回为最大值)
// 若希望 top() 返回最小值,可令比较类型为 greater<TypeName>
// 注意:不可跳过 Container 直接传入 Compare

// 从 C++11 开始,如果使用 lambda 函数自定义 Compare
// 则需要将其作为构造函数的参数代入,如:
auto cmp = [](const std::pair<int, int> &l, const std::pair<int, int> &r) {
  return l.second < r.second;
};
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int> >,
                    decltype(cmp)>
    pq(cmp);

成员函数

以下所有函数均为常数复杂度

  • top() 访问堆顶元素(此时优先队列不能为空)
  • empty() 询问容器是否为空
  • size() 查询容器中的元素数量

以下所有函数均为对数复杂度

  • push(x) 插入元素,并对底层容器排序
  • pop() 删除堆顶元素(此时优先队列不能为空)

简单示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
std::priority_queue<int> q1;
std::priority_queue<int, std::vector<int> > q2;
// C++11 后空格可省略
std::priority_queue<int, std::deque<int>, std::greater<int> > q3;
// q3 为小根堆
for (int i = 1; i <= 5; i++) q1.push(i);
// q1 中元素 :  [1, 2, 3, 4, 5]
std::cout << q1.top() << std::endl;
// 输出结果 : 5
q1.pop();
// 堆中元素 : [1, 2, 3, 4]
std::cout << q1.size() << std::endl;
// 输出结果 :4
for (int i = 1; i <= 5; i++) q3.push(i);
// q3 中元素 :  [1, 2, 3, 4, 5]
std::cout << q3.top() << std::endl;
// 输出结果 : 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值