C++ 标准模板库 (STL, Standard Template Library):包含一些常用数据结构与算法的模板的 C++ 软件库。其包含四个组件——算法 (Algorithms)、容器 (Containers)、仿函数 (Functors)、迭代器 (Iterators).
示例:
- 算法:
sort(a.begin(), a.end())
- 容器:
priority_queue<int> pque
- 仿函数:
greater<int>()
- 迭代器:
vector<int>::iterator it = a.begin()
1.1 内容总览
打勾的是本次将会详细讲解的,加粗的是算法竞赛中有必要学习的。
-
顺序容器
-
array
-
vector
-
deque
-
forward_list
-
list
-
-
关联容器
- set
- map
- multiset
- multimap
-
无序关联容器
- unordered_set
- unordered_map
- unordered_multiset
- unordered_multimap
-
容器适配器
- stack
- queue
- priority_queue
- flat_set
- flat_map
- flat_multiset
- flat_multimap
-
字符串
- string (basic_string<char>)
-
对与元组
- pair
- tuple
1.2 向量 vector
#include <vector>
连续的顺序的储存结构(和数组一样的类别),但是有长度可变的特性。
1.2.1 常用方法
构造
vector<类型> arr(长度, [初值])
时间复杂度: O ( n ) O(n) O(n)
常用的一维和二维数组构造示例,高维也是一样的(就是会有点长).
vector<int> arr; // 构造int数组
vector<int> arr(100); // 构造初始长100的int数组
vector<int> arr(100, 1); // 构造初始长100的int数组,初值为1
vector<vector<int>> mat(100, vector<int> ()); // 构造初始100行,不指定列数的二维数组
vector<vector<int>> mat(100, vector<int> (666, -1)) // 构造初始100行,初始666列的二维数组,初值为-1
构造二维数组的奇葩写法,千万别用:
vector<int> arr[100]; // 正确,构造初始100行,不指定列数的二维数组,可用于链式前向星存图
vector<int> arr[100](100, 1); // 语法错误!
vector<int> arr(100, 1)[100]; // 语法错误!
vector<int> arr[100] {{100, 1}, 这里省略98个 ,{100, 1}}; // 正确但奇葩,使用列表初始化
尾接 & 尾删
.push_back(元素)
:在 vector 尾接一个元素,数组长度 + 1 +1 +1..pop_back()
:删除 vector 尾部的一个元素,数组长度 − 1 -1 −1
时间复杂度:均摊 O ( 1 ) O(1) O(1)
// init: arr = []
arr.push_back(1);
// after: arr = [1]
arr.push_back(2);
// after: arr = [1, 2]
arr.pop_back();
// after: arr = [1]
arr.pop_back();
// after: arr = []
获取长度
.size()
获取当前 vector 的长度
时间复杂度: O ( 1 ) O(1) O(1)
for (int i = 0; i < arr.size(); i++)
cout << a[i] << endl;
清空
.clear()
清空 vector
时间复杂度: O ( n ) O(n) O(n)
判空
.empty()
如果是空返回 true
反之返回 false
.
时间复杂度: O ( 1 ) O(1) O(1)
改变长度
.resize(新长度, [默认值])
修改 vector 的长度
- 如果是缩短,则删除多余的值
- 如果是扩大,且指定了默认值,则新元素均为默认值 (旧元素不变)
时间复杂度: O ( n ) O(n) O(n)
1.2.2 适用情形
一般情况 vector
可以替换掉普通数组,除非该题卡常。
有些情况普通数组没法解决: n × m n\times m n×m 的矩阵, 1 ≤ n , m ≤ 1 0 6 1\leq n,m\leq 10^6 1≤n,m≤106 且 n × m ≤ 1 0 6 n\times m \leq 10^6 n×m≤106
- 如果用普通数组
int mat[1000010][1000010]
,浪费内存,会导致 MLE。 - 如果使用
vector<vector<int>> mat(n + 10, vector<int> (m + 10))
,完美解决该问题。
另外,vector
的数据储存在堆空间中,不会爆栈。
1.2.3 注意事项
提前指定长度
如果长度已经确定,那么应当直接在构造函数指定长度,而不是一个一个 .push_back()
. 因为 vector
额外内存耗尽后的重分配是有时间开销的,直接指定长度就不会出现重分配了。
// 优化前: 522ms
vector<int> a;
for (int i = 0; i < 1e8; i++)
a.push_back(i);
// 优化后: 259ms
vector<int> a(1e8);
for (int i = 0; i < a.size(); i++)
a[i] = i;
当心 size_t 溢出
vector 获取长度的方法 .size()
返回值类型为 size_t
,通常 OJ 平台使用的是 32 位编译器(有些平台例如 cf 可选 64 位),那么该类型范围为
[
0
,
2
32
)
[0,2^{32})
[0,232).
vector<int> a(65536);
long long a = a.size() * a.size(); // 直接溢出变成0了
Code
int n;
cin >> n;
vector<int> vv(n);//开辟 n 个空间,全部初始化为 0
vector<int> vec, e[110];//动态添加
//clear() 清空容器
vec.clear();
for (int i = 1; i <= n; i++)
e[i].clear();//e->clear()是错的!
for (int i = 1; i <= n; i++) {
vec.push_back(3);//末尾放入
vec[i] = 2;//修改
}
vec.pop_back();//末尾删除
//insert,插入一个数到容器内某个位置,留给你们自行了解
//排序
sort(vec.begin(), vec.end());
for (int i = 1; i <= n; i++)
sort(e[i].begin(), e[i].end());
//查找
int t = lower_bound(vec.begin(), vec.end(), 3) - vec.begin();
//找到第一个大于等于3的数,返回它在vec容器内的下标,如果不存在会返回vec.end()
//删除,需要保证指针链接性
for (auto it = vec.begin(); it != vec.end();) {
if (*it == 4)it = vec.erase(it);//erase函数会返回删除该位的下一位的指针
it++;
}
//遍历,常用两种
for (int i = 0; i < vec.size(); i++)
cout << vec[i] << ' ';
cout << endl;
for (auto j : vec)
cout << j << ' ';
//输出开头和末尾的数
cout << vec.front() << endl;
cout << vec.back();
1.3 栈 stack
#include <stack>
通过二次封装双端队列 (deque) 容器,实现先进后出的栈数据结构。
1.3.1 常用方法
作用 | 用法 | 示例 |
---|---|---|
构造 | stack<类型> stk | stack<int> stk; |
进栈 | .push(元素) | stk.push(1); |
出栈 | .pop() | stk.pop(); |
取栈顶 | .top() | int a = stk.top(); |
查看大小 / 清空 / 判空 | 略 | 略 |
1.3.2 适用情形
如果不卡常的话,就可以直接用它而不需要手写栈了。
另外,vector 也可以当栈用,vector 的 .back()
取尾部元素,就相当于取栈顶,.push_back()
相当于进栈,.pop_back()
相当于出栈。
1.3.3 注意事项
不可访问内部元素!下面都是错误用法
for (int i = 0; i < stk.size(); i++)
cout << stk[i] << endl;
for (auto ele : stk)
cout << stk << endl;
1.4 队列 queue
#include <queue>
通过二次封装双端队列 (deque) 容器,实现先进先出的队列数据结构。
1.4.1 常用方法
作用 | 用法 | 示例 |
---|---|---|
构造 | queue<类型> que | queue<int> que; |
进队 | .push(元素) | que.push(1); |
出队 | .pop() | que.pop(); |
取队首 | .front() | int a = que.front(); |
取队尾 | .back() | int a = que.back(); |
查看大小 / 清空 / 判空 | 略 | 略 |
1.4.2 适用情形
如果不卡常的话,就可以直接用它而不需要手写队列了。
1.4.3 注意事项
不可访问内部元素!下面都是错误用法
for (int i = 0; i < que.size(); i++)
cout << que[i] << endl;
for (auto ele : que)
cout << ele << endl;
1.5 优先队列 priority_queue
#include <queue>
提供常数时间的最大元素查找,对数时间的插入与提取,底层原理是二叉堆。
1.5.1 常用方法
构造
priority_queue<类型, 容器, 比较器> pque
- 类型:要储存的数据类型
- 容器:储存数据的底层容器,默认为
vector<类型>
,竞赛中保持默认即可 - 比较器:比较大小使用的比较器,默认为
less<类型>
,可自定义
priority_queue<int> pque1; // 储存int的大顶堆
priority_queue<int, vector<int>, greater<int>> pque2; // 储存int的小顶堆
对于需要自定义比较器的情况,涉及一些初学时容易看迷糊的语法(重载小括号运算符 / lambda 表达式),在此就不展开讲了。如果想要了解,可以查阅 cppreference 中的代码示例。
其他
作用 | 用法 | 示例 |
---|---|---|
进堆 | .push(元素) | que.push(1); |
出堆 | .pop() | que.pop(); |
取堆顶 | .top() | int a = que.top(); |
查看大小 / 判空 | 略 | 略 |
进出队复杂度 O ( log n ) O(\log n) O(logn),取堆顶 O ( 1 ) O(1) O(1).
1.5.2 适用情形
持续维护元素的有序性:每次向队列插入大小不定的元素,或者每次从队列里取出大小最小/最大的元素,元素数量 n n n,插入操作数量 k k k.
- 每次插入后进行快速排序: k ⋅ n log n k\cdot n\log n k⋅nlogn
- 使用优先队列维护: k ⋅ log n k\cdot\log n k⋅logn
1.5.3 注意事项
仅堆顶可读
只可访问堆顶,其他元素都无法读取到。下面是错误用法:
cout << pque[1] << endl;
所有元素不可写
堆中所有元素是不可修改的。下面是错误用法:
pque[1] = 2;
pque.top() = 1;
如果你恰好要修改的是堆顶元素,那么是可以完成的:
int tp = pque.top();
pque.pop();
pque.push(tp + 1);
Code
//自动排序内部数,小到大或大到小
priority_queue<int, vector<int>, greater<int>> q1;
//greater是小根堆,用vector装载内部元素
priority_queue<int, vector<int>, less<int>> q2;
//less是大根堆
q1.push(3), q2.push(2);
while (q1.size())
{
int t = q1.top();//小根堆所以取出的数是所有数内最小的
q1.pop();
cout << t << ' ';
}
1.6 集合 set
#include <set>
提供对数时间的插入、删除、查找的集合数据结构。底层原理是红黑树。
集合三要素 | 解释 | set | multiset | unordered_set |
---|---|---|---|---|
确定性 | 一个元素要么在集合中,要么不在 | ✔ | ✔ | ✔ |
互异性 | 一个元素仅可以在集合中出现一次 | ✔ | ❌(任意次) | ✔ |
无序性 | 集合中的元素是没有顺序的 | ❌(从小到大) | ❌(从小到大) | ✔ |
1.6.1 常用方法
构造
set<类型, 比较器> st
- 类型:要储存的数据类型
- 比较器:比较大小使用的比较器,默认为
less<类型>
,可自定义
set<int> st1; // 储存int的集合(从小到大)
set<int, greater<int>> st2; // 储存int的集合(从大到小)
对于需要自定义比较器的情况,涉及一些初学时容易看迷糊的语法(重载小括号运算符 / lambda 表达式),在此就不展开讲了。
遍历
可使用迭代器进行遍历:
for (set<int>::iterator it = st.begin(); it != st.end(); ++it)
cout << *it << endl;
基于范围的循环(C++ 11):
for (auto &ele : st)
cout << ele << endl;
其他
作用 | 用法 | 示例 |
---|---|---|
插入元素 | .insert(元素) | st.insert(1); |
删除元素 | .erase(元素) | st.erase(2); |
查找元素 | .find(元素) | auto it = st.find(1); |
判断元素是否存在 | .count(元素) | st.count(3); |
查看大小 / 清空 / 判空 | 略 | 略 |
增删查时间复杂度均为 O ( log n ) O(\log n) O(logn)
1.6.2 适用情形
- 元素去重: [ 1 , 1 , 3 , 2 , 4 , 4 ] → [ 1 , 2 , 3 , 4 ] [1,1,3,2,4,4]\to[1,2,3,4] [1,1,3,2,4,4]→[1,2,3,4]
- 维护顺序: [ 1 , 5 , 3 , 7 , 9 ] → [ 1 , 3 , 5 , 7 , 9 ] [1,5,3,7,9]\to[1,3,5,7,9] [1,5,3,7,9]→[1,3,5,7,9]
- 元素是否出现过:元素大小 [ − 1 0 18 , 1 0 18 ] [-10^{18},10^{18}] [−1018,1018],元素数量 1 0 6 10^6 106,vis 数组无法实现,通过 set 可以完成。
1.6.3 注意事项
不存在下标索引
set 虽说可遍历,但仅可使用迭代器进行遍历,它不存在下标这一概念,无法通过下标访问到数据。下面是错误用法:
cout << st[0] << endl;
元素只读
set 的迭代器取到的元素是只读的(因为是 const 迭代器),不可修改其值。如果要改,需要先 erase 再 insert. 下面是错误用法:
cout << *st.begin() << endl; // 正确。可读。
*st.begin() = 1; // 错误!不可写!
不可用迭代器计算下标
set 的迭代器不能像 vector 一样相减得到下标。下面是错误用法:
auto it = st.find(2); // 正确,返回2所在位置的迭代器。
int idx = it - st.begin(); // 错误!不可相减得到下标。
1.7 映射 map
#include <map>
提供对数时间的有序键值对结构。底层原理是红黑树。
映射:
1 → 2 2 → 2 3 → 1 4 → 5 ⋮ \begin{matrix} 1&\to&2\\ 2&\to&2\\ 3&\to&1\\ 4&\to&5\\ &\vdots \end{matrix} 1234→→→→⋮2215
性质 | 解释 | map | multimap | unordered_map |
---|---|---|---|---|
互异性 | 一个键仅可以在映射中出现一次 | ✔ | ❌(任意次) | ✔ |
无序性 | 键是没有顺序的 | ❌(从小到大) | ❌(从小到大) | ✔ |
1.7.1 常用方法
构造
map<键类型, 值类型, 比较器> mp
- 键类型:要储存键的数据类型
- 值类型:要储存值的数据类型
- 比较器:键比较大小使用的比较器,默认为
less<类型>
,可自定义
map<int, int> mp1; // int->int 的映射(键从小到大)
map<int, int, greater<int>> st2; // int->int 的映射(键从大到小)
对于需要自定义比较器的情况,涉及一些初学时容易看迷糊的语法(重载小括号运算符 / lambda 表达式),在此就不展开讲了。
遍历
可使用迭代器进行遍历:
for (map<int, int>::iterator it = mp.begin(); it != mp.end(); ++it)
cout << it->first << ' ' << it->second << endl;
基于范围的循环(C++ 11):
for (auto &pr : mp)
cout << pr.first << ' ' << pr.second << endl;
结构化绑定 + 基于范围的循环(C++17):
for (auto &[key, val] : mp)
cout << key << ' ' << val << endl;
其他
作用 | 用法 | 示例 |
---|---|---|
增 / 改 / 查元素 | 中括号 | mp[1] = 2; |
查元素(返回迭代器) | .find(元素) | auto it = mp.find(1); |
删除元素 | .erase(元素) | mp.erase(2); |
判断元素是否存在 | .count(元素) | mp.count(3); |
查看大小 / 清空 / 判空 | 略 | 略 |
增删改查时间复杂度均为 O ( log n ) O(\log n) O(logn)
1.7.2 适用情形
需要维护映射的场景可以使用:输入若干字符串,统计每种字符串的出现次数。(map<string, int> mp
)
1.7.3 注意事项
中括号访问时默认值
如果使用中括号访问 map 时对应的键不存在,那么会新增这个键,并且值为默认值,因此中括号会影响键的存在性。
map<char, int> mp;
cout << mp.count('a') << endl; // 0
mp['a']; // 即使什么都没做,此时mp['a']=0已经插入了
cout << mp.count('a') << endl; // 1
cout << mp['a'] << endl; // 0
不可用迭代器计算下标
map 的迭代器不能像 vector 一样相减得到下标。下面是错误用法:
auto it = mp.find('a'); // 正确,返回2所在位置的迭代器。
int idx = it - mp.begin(); // 错误!不可相减得到下标。
Code
// 红黑树结构,可以自动排序去重
set<int> se;
// 关键字 关键值
map<int, int> mp; // map相当于拥有了关键值的set
// 插入
se.insert(3); // 插入一个 3 关键字
mp.insert({1, 2}); // 插入一个 1 关键字,并给这个关键字的关键值赋值 2
mp.insert({1, 1}); // 因为 1 关键字已经存在,所以把关键值修改成 1
// mp.insert({ 1,2 }) 等价于
mp[1] = 2;
mp[1] += 2; // 1 关键字的关键值 + 2
// 遍历
for (auto j : se)
cout << j << endl;
for (auto j : mp)
cout << j.first << ' ' << j.second << endl; // 两个值
for (auto it = mp.begin(); it != mp.end(); it++)
cout << (*it).first << ' ' << (*it).second << endl;
// 无括号会报错
for (auto it = se.rbegin(); it != se.rend(); it++) // 反向遍历
cout << *it << endl;
// 删除
mp.erase(3);
se.erase(3); // 删除关键字为 3 的元素
// 如果是遍历删除 map 内特定的第 K 个数,跟 vector 删除一样要注意指针衔接
int k = 0;
for (auto it = mp.begin(); it != mp.end(); k++)
{
if (k == K)
it = mp.erase(it);
else
it++;
}
int t = mp.count(3); // 返回指定元素出现的次数
t = mp.size(); // 返回map中元素的个数
// 查找
auto j = mp.find(3); // 查找 3 关键字在 map 内的下标
// 如果不存在则 j == mp.end()
if (j != mp.end())
cout << (*j).first;
auto g = mp.lower_bound(3); // 查找 map 内第一个大于等于 3 的数的下标
// 如果不存在则 g == mp.end()
if (g != mp.end())
cout << (*g).first;
1.8 字符串 string
#include <string>
顾名思义,就是储存字符串的。
1.8.1 常用方法
构造
构造函数:string(长度, 初值)
string s1; // 构造字符串,为空
string s2 = "awa!"; // 构造字符串,并赋值awa!
string s3(10, '6'); // 构造字符串,通过构造函数构造为6666666666
其他
作用 | 用法 | 示例 |
---|---|---|
是否相同 | == | if (s1 == s2) ... |
字符串连接 | + | string s = s1 + s2; |
尾接字符串 | += | s += "awa"; |
取子串 | .substr(起始下标, 子串长度) | string sub = s.substr(2, 10); |
查找字符串 | .find(字符串, 起始下标) | int pos = s.find("awa"); |
插入字符 | .insert() | s.insert(0,"heo"); s.insert(4,"world",2); |
替换字符 | .replace(起始下标, 子串长度) | s = s.replace(s.find(“c”),2,“#”); //从第一个 a 位置开始的两个字符替换成# |
数值与字符串互转(C++11)
最常用的是 stoll 和 stod
stoll 可以兼容 stoi,stol stod 可以兼容 stof。
源 | 目的 | 函数 |
---|---|---|
int / long long / float / double / long double | string | to_string() |
string | int | stoi() |
string | long long | stoll() |
string | float | stof() |
string | double | stod() |
string | long double | stold() |
1.8.2 注意事项
尾接字符串一定要用 +=
string 的 += 运算符,将会在原字符串原地尾接字符串。而 + 了再 = 赋值,会先生成一个临时变量,在复制给 string.
通常字符串长度可以很长,如果使用 + 字符串很容易就 TLE 了。
//多用+=
// 优化前: 15139ms
string s;
for (int i = 0; i < 5e5; i++)
s = s + "a";
// 优化后: < 1ms (计时器显示0)
string s;
for (int i = 0; i < 5e5; i++)
s += "a";
.substr()
方法的奇葩参数
一定要注意,C++ string 的取子串的第一个参数是子串起点下标,第二个参数是子串长度。
第二个参数不是子串终点!不是子串终点!要与 java 等其他语言区分开来。
若 pos 的值超过了 string 的大小,则 substr 函数会抛出一个 out_of_range 异常;
若 pos+n 的值超过了 string 的大小,则 substr 会调整 n 的值,只拷贝到 string 的末尾
.find()
方法的复杂度
该方法实现为暴力实现,时间复杂度为 O ( n 2 ) O(n^2) O(n2).
不要幻想 STL 内置了个
O
(
n
)
O(n)
O(n) 的 KMP 算法
Code
string a, b;
a.clear(), b.clear();//清空
cin >> a >> b;//输入
a.push_back('b');//最末尾添加一个字符
a.pop_back();//删除最末尾一个字符
a += b;//b字符串接到a后部,无 a -= b 语法
//遍历
for (int i = 0; i < a.size(); i++) {
cout << a[i] << ' ';//输出每个位置字符
a[i] = 'b';//修改
}
//find,找到第一个 x 字符的下标
int t = a.find('a');
//substr,取子串函数
string g = a.substr(0, 3);//0 下标开始包括此位置往后取 3 个字符
g = a.substr(3, 3);
g = a.substr(3);//3下标开始取到结束
1.9 二元组 pair
#include <utility>
顾名思义,就是储存二元组的。
1.9.1 常用方法
构造
pair<第一个值类型, 第二个值类型> pr
- 第一个值类型:要储存的第一个值的数据类型
- 第二个值类型:要储存的第二个值的数据类型
pair<int, int> p1;
pair<int, long long> p2;
pair<char, int> p3;
// ...
赋值
老式
pair<int, char> pr = make_pair(1, 'a');
列表构造 C++11
pair<int, char> pr = {1, 'a'};
取值
直接取值
- 取第一个值:
.first
- 取第二个值:
.second
pair<int, char> pr = {1, 'a'};
int awa = pr.first;
char bwb = pr.second;
结构化绑定 C++17
pair<int, char> pr = {1, 'a'};
auto &[awa, bwb] = pr;
判同
直接用 ==
运算符
pair<int, int> p1 = {1, 2};
pair<int, int> p2 = {1, 3};
if (p1 == p2) { ... } // false
1.9.2 适用场景
所有需要二元组的场景均可使用,效率和自己定义结构体差不多。
Code
//一个变量携带两个值
pair<int, int> pii;
cin >> pii.first >> pii.second;
cout << pii.first << ' ' << pii.second;
//通常作为工具与其他容器结合使用
2 迭代器简介
2.1 迭代器是什么?
对于一个 vector,我们可以用下标遍历:
for (int i = 0; i < a.size(); i++)
cout << a[i] << endl;
我们同时也可以用迭代器来遍历:
for (vector<int>::iterator it = a.begin(); it != a.end(); ++it)
cout << *it << endl;
a.begin()
是一个迭代器,指向的是第一个元素a.end()
是一个迭代器,指向的是最后一个元素再后面一位- 上述迭代器具有自增运算符,自增则迭代器向下一个元素移动
- 迭代器与指针相似,如果对它使用解引用运算符,即
*it
,就能取到对应值了
2.2 为何需要迭代器?
很多数据结构并不是线性的(例如红黑树),对于非线性数据结构,下标是无意义的。无法使用下标来遍历整个数据结构。
迭代器的作用就是定义某个数据结构的遍历方式,通过迭代器的增减,代表遍历到的位置,通过迭代器便能成功遍历非线性结构了。
例如,set 的实现是红黑树,我们是没法用下标来访问元素的。但是通过迭代器,我们就能遍历 set 中的元素了:
for (set<int>::iterator it = st.begin(); it != st.end(); ++it)
cout << *it << endl;
2.3 迭代器用法
对于 vector 容器,它的迭代器功能比较完整,以它举例:
.begin()
:头迭代器.end()
:尾迭代器.rbegin()
:反向头迭代器.rend()
:反向尾迭代器- 迭代器
+
整型:将迭代器向后移动 - 迭代器
-
整型:将迭代器向前移动 - 迭代器
++
:将迭代器向后移动 1 位 - 迭代器
--
:将迭代器向前移动 1 位 - 迭代器
-
迭代器:两个迭代器的距离 prev(it)
:返回 it 的前一个迭代器next(it)
:返回 it 的后一个迭代器
对于其他容器,由于其结构特性,上面的功能不一定都有(例如 set 的迭代器是不能相减求距离的)
2.4 常见问题
.end()
和 .rend()
指向的位置是无意义的值
对于一个长度为 10 的数组:for (int i = 0; i < 10; i++)
,第 10 位是不可访问的
对于一个长度为 10 的容器:for (auto it = a.begin(); it != a.end(); ++it)
,.end 是不可访问的
不同容器的迭代器功能可能不一样
迭代器细化的话有正向、反向、双向,每个容器的迭代器支持的运算符也可能不同,因此不同容器的迭代器细节很有可能是不一样的。
删除操作时需要警惕
为什么 3 没删掉?
vector<int> a{1, 2, 3, 4};
for (auto it = a.begin(); it != a.end(); ++it)
if (*it == 2 || *it == 3)
a.erase(it);
// a = [1, 3, 4]
为啥 RE 了?
vector<int> a{1, 2, 3, 4};
for (auto it = a.begin(); it != a.end(); ++it)
if (*it == 4)
a.erase(it);
补充
22、STL
1.优先队列 priority_queue
empty()如果队列为空返回真
pop()删除对顶元素
push()加入一个元素
size()返回优先队列中拥有的元素个数
top()返回优先队列队顶元素
在默认的优先队列中,优先级高的先出队。在默认的int型中先出队的为较大的数。
priority_queueq1;//大的先出对
priority_queue<int,vector,greater >q2; //小的先出队
自定义比较函数:
struct cmp
{
bool operator ()(int x, int y)
{
return x > y; // x小的优先级高
//也可以写成其他方式,如: return p[x] > p[y];表示p[i]小的优先级高
}
};
priority_queue<int, vector, cmp>q;//定义方法
//其中,第二个参数为容器类型。第三个参数为比较函数。
结构体排序:
struct node
{
int x, y;
friend bool operator < (node a, node b)
{
return a.x > b.x; //结构体中,x小的优先级高
}
};
priority_queueq;//定义方法
//在该结构中,y为值, x为优先级。
//通过自定义operator<操作符来比较元素中的优先级。
//在重载”<”时,最好不要重载”>”,可能会发生编译错误
2.set 和 multiset
set和 multiset用法一样,就是 multiset允许重复元素。
元素放入容器时,会按照一定的排序法则自动排序,默认是按照 less<>排序规则来排序。不
能修改容器里面的元素值,只能插入和删除。
自定义 int排序函数:(默认的是从小到大的,下面这个从大到小)
struct classcomp {
bool operator() (const int& lhs, const int& rhs) const
{return lhs>rhs;}
};//这里有个逗号的,注意
multiset<int,classcomp> fifth;
// class as Compare
上面这样就定义成了从大到小排列了。
结构体自定义排序函数:
(定义 set或者 multiset的时候定义了排序函数,定义迭代器时一样带上排序函数)
struct Node
{
int x,y;
};
struct classcomp//先按照 x从小到大排序,x相同则按照y从大到小排序
{
bool operator()(const Node &a,const Node &b)const
{
if(a.x!=b.x)return a.x<b.x;
else return a.y>b.y;
}
}; //注意这里有个逗号
multiset<Node,classcomp>mt;
multiset<Node,classcomp>::iterator it;
Multiset //该函数是set的多重集合形式可保存eg:1 2 2 3 3 4 5
multimap<int, int>a;
int main(){
a.insert(pair<int,int>(1, 1));
a.insert(pair<int, int>(2, 1));
a.insert(pair<int, int>(1, 2));
for (multimap<int, int>::iterator it = a.begin(); it != a.end(); it++)
cout << (*it).first<<" "<<(*it).second << endl;
return 0;
}
主要函数:
begin()返回指向第一个元素的迭代器
clear()清除所有元素
count()返回某个值元素的个数
empty()如果集合为空,返回 (true)
end()返回指向最后一个元素的迭代器
erase()删除集合中的元素 (参数是一个元素值,或者迭代器 )
find()返回一个指向被查找到元素的迭代器
insert()在集合中插入元素
size()集合中元素的数目
lower_bound()返回指向大于(或等于)某值的第一个元素的迭代器
upper_bound()返回大于某个值元素的迭代器
equal_range()返回集合中与给定值相等的上下限的两个迭代器
(注意对于 multiset删除操作之间删除值会把所以这个值的都删掉,删除一个要用迭代器 )
3.lower_bound and upper_bound
lower_bound() //在first和last中的前闭后开区间进行二分查找(故要先sort()),返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置(且a[last]不存在)
Eg: Pos= lower_bound(a,a+N,val)-a;
upper_bound() //也是前闭后开区间,且返回第一个大于val的位置,如果所有元素都小于val,则返回last的位置。
Eg: Pos= upper_bound (a,a+N,val)-a;
4.next_permutation and prev_permutation
next_permutation() //排列组合使用,eg:1 2 3 4 =》1 2 4 3 =》1 3 2 4 =》…
prev_permutation() //和上面的相反,返回上一项结果
Eg: next_permutation(a,a+N); //执行成功返回1,否则返回0
5.reverse()
//可以对数组,字符串,vector等进行翻转操作123=》321
Eg: reverse(a,a+N);
6.Map
map<int, char>m;
map<int, char>::iterator it;
m[8] = ‘a’;
m[6] = ‘b’;
m[11] = ‘c’;
it = m.begin();
cout << it->first << " " << it->second << endl; //输出6 b
it = m.end(); it–;
cout << it->first << " " << it->second << endl; //输出11 c
//map按照平衡二叉树原理类似,begin()为最左下角(min)
//end()为最右下角(max)