- STL
STL
1 容器
-
- 序列容器
- string
- vector 动态数组
- deque 双端队列
- list 双向链表
- forward_list 单向链表
- 序列容器
-
- 关联容器
- set
- unordered_set
- unordered_multiset
- map
- unordered_map
- unordered_multimap
- 关联容器
-
- 容器适配器
- stack
- queue
- priority_queue 2 算法 3 容器迭代器
- 容器适配器
1 容器
1.0 总结
1.0.1 容器特性总结
1. 序列容器
- string
- vector:可随机访问,适用于频繁在尾部增删
- deque:可随机访问,适用于频繁在两端增删
- list:双向链表,不可随机访问,适用于需要频繁在任意位置增删时
-
forward_list:单向链表,内存占用更少
vector:动态数组- 连续内存存储
- 支持随机访问 O(1)
- 尾部插入删除:摊还 O(1)(扩容时可能 O(n))
- 中间插入删除 O(n)
- 自动扩容(2倍)
deque:双端队列- 分段连续内存
- 支持随机访问 O(1)
- 两端插入删除:摊还 O(1)
- 中间插入删除 O(n)
- 不需要整体重新分配内存
list:双向链表- 非连续内存
- 不支持随机访问 O(n)
- 任意位置插入删除 O(1),不需要移动元素
forward_list:单向链表- 比list更节省内存
- 只能单向遍历
- 不支持随机访问
2. 关联容器
- set/multiset:需要有序存储,频繁查找
- map/multimap:需要有序键值对,频繁查找
- unordered_set/unordered_multiset:需要快速查找,不要求有序
-
unordered_map/unordered_multimap:需要快速查找键值对,不要求有序
set/multiset:- 底层红黑树
- 自动排序
- 查找 O(log n)
- set不允许重复,multiset允许重复
map/multimap:- 键值对存储
- 底层红黑树
- 按键自动排序
- 查找 O(log n)
- map不允许重复键,multimap允许
unordered_set/unordered_multiset:- 底层哈希表
- 查找:平均/摊还 O(1),最坏 O(n)
- 不保证顺序
- 需要哈希函数
unordered_map/unordered_multimap:- 键值对存储
- 底层哈希表
- 查找:平均/摊还 O(1),最坏 O(n)
- 不保证顺序
3. 容器适配器
stack:栈- 默认基于deque
- 先进后出
- 只允许访问栈顶
queue:队列- 默认基于deque
- 先进先出
- 只允许访问队首和队尾
priority_queue:优先队列- 默认基于vector
- 堆结构(保证 top 是当前“最优”元素,并不维护全局有序)
- 只允许访问队首
1.0.2 容器性能总结
| 操作 | vector | deque | list | set | map | unordered_set | unordered_map |
|---|---|---|---|---|---|---|---|
| 随机访问 | O(1) | O(1) | O(n) | O(log n) | O(log n) | —(不支持) | —(不支持) |
| 头部插入 | O(n) | O(1) | O(1) | O(log n) | O(log n) | 平均 O(1) | 平均 O(1) |
| 尾部插入 | 摊还 O(1) | O(1) | O(1) | O(log n) | O(log n) | 平均 O(1) | 平均 O(1) |
| 中间插入 | O(n) | O(n) | O(1) | O(log n) | O(log n) | 平均 O(1) | 平均 O(1) |
| 查找 | O(n) | O(n) | O(n) | O(log n) | O(log n) | 平均 O(1) | 平均 O(1) |
1.0.3 容器提供的方法表
| 方法 \ 容器 | string | vector | deque | list | forward_list | set / multiset | unordered_set | map / multimap | unordered_map | stack | queue | priority_queue |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 访问方法 | ||||||||||||
| operator[] | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ |
| at | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ |
| data | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| front | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ |
| back | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ |
| top | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✓ |
| 迭代器方法 | ||||||||||||
| begin、end | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| rbegin、rend | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ |
| 修改方法 | ||||||||||||
| push_back、emplace_back | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| pop_back | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| push_front、emplace_front | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| pop_front | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| pop | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ |
| push | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ |
| emplace | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| insert (pos)、insert (range)、erase (single)、erase (range) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| 容量方法 | ||||||||||||
| size | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| max_size | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| empty | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| clear | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| resize | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| reserve | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ |
| capacity | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| shrink_to_fit | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| swap | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| assign | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| 查找方法 | ||||||||||||
| find | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| count | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| lower_bound、upper_bound | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ |
| equal_range | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
| string专用 | ||||||||||||
| substr | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| c_str | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| replace | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
1.0.4 方法说明
| 方法 | 说明 |
|---|---|
| 访问方法 | |
| front | 返回第一个元素的引用,不检查容器是否为空(空容器调用未定义行为) |
| back | 返回最后一个元素的引用,不检查容器是否为空(空容器调用未定义行为) |
| top | 返回栈顶/优先队列顶部元素的引用(stack/priority_queue专用) |
| 迭代器方法 | |
| begin、end | 返回指向首元素和尾后元素的迭代器,用于遍历容器 |
| rbegin、rend | 返回反向迭代器,从尾部向头部遍历(不支持反向遍历的容器没有此方法) |
| 修改方法 | |
| push_back、emplace_back | 在容器尾部添加元素;emplace_back直接构造,避免拷贝/移动,效率更高 |
| pop_back | 删除容器尾部元素,不返回值(需要先访问再删除) |
| push_front、emplace_front | 在容器头部添加元素(deque/list/forward_list支持) |
| pop_front | 删除容器头部元素,不返回值(deque/list/forward_list支持) |
| pop | 删除栈顶/队首元素,不返回值(stack/queue/priority_queue专用) |
| push | 添加元素到栈/队列(stack/queue/priority_queue专用) |
| emplace | 在指定位置直接构造元素,避免拷贝/移动(关联容器和适配器支持) |
| insert (pos)、insert (range) | 在指定位置插入元素/范围;返回插入位置下一个位置的迭代器 |
| erase (single)、erase (range) | 删除单个元素/范围;返回删除后下一个位置的迭代器 |
| 容量方法 | |
| size | 返回容器中元素的数量,O(1)时间复杂度 |
| max_size | 返回容器能容纳的最大元素数量(理论值,通常很大) |
| empty | 检查容器是否为空,返回bool值,比size()==0更高效 |
| clear | 清空容器所有元素,调用每个元素的析构函数 |
| resize | 改变容器大小:n>size时扩展并填充默认值/指定值,n<size时缩小;必要时会改变capacity |
| reserve | 预分配容量(capacity),只改变capacity不改变size,不创建元素;用于避免频繁扩容 |
| capacity | 返回当前分配的容量(capacity),capacity>=size(vector/string专用) |
| shrink_to_fit | 请求释放多余内存,将capacity缩小到size(请求可能被实现忽略) |
| swap | 交换两个容器的内容,O(1)时间复杂度(通常只交换内部指针) |
| assign | 用新内容替换容器内容,可以指定数量+值、范围、初始化列表等 |
| 查找方法 | |
| find | 查找指定元素,返回指向该元素的迭代器,未找到返回end();关联容器O(log n),无序容器平均/摊还 O(1)(最坏O(n)) |
| count | 返回指定元素的数量;set/map返回0或1,multiset/multimap返回实际数量 |
| lower_bound | 返回指向第一个>=(lower_bound)指定值的迭代器(有序容器专用) |
| upper_bound | 返回指向第一个>(upper_bound)指定值的迭代器(有序容器专用) |
| equal_range | 返回pair<lower_bound, upper_bound>,表示等于指定值的元素范围(有序容器专用) |
| string专用 | |
| substr | 返回子字符串,参数:起始位置和长度(可选) |
| c_str | 返回C风格字符串(const char*),以’\0’结尾 |
| replace | 替换字符串的指定部分,有多种重载形式(位置+长度、迭代器范围等) |
1.0.5 方法总结
序列容器 (vector, deque, list, forward_list)
- 共同特点:支持
insert,erase,size,clear,empty,swap,assign - 迭代器:
begin,end,除 forward_list 外都支持反向迭代器 - 访问差异:
vector/deque:支持operator[],at随机访问list/forward_list:只能通过迭代器顺序访问
- 修改操作:
vector:只支持尾部push_back/pop_backdeque:支持两端操作push_front/back,pop_front/backlist:支持两端操作,额外提供merge,sort,reverse,uniqueforward_list:只支持前端操作push_front/pop_front
关联容器 (set, map 及其 multi/unordered 版本)
- 共同操作:
insert,erase,find,count,size,clear,empty,swap - 有序容器 (set/map):额外提供
lower_bound,upper_bound,equal_range - 映射容器 (map):支持
operator[],at进行键值访问
适配器容器 (stack, queue, priority_queue)
- 受限接口:不提供迭代器,只提供特定的访问和修改操作
stack:top,push,popqueue:front,back,push,poppriority_queue:top,push,pop- 共同操作:
size,empty,swap
1.1 string
创建与初始化
1
2
3
4
5
6
7
8
9
10
11
string a="adwdad";
string a[4] = {"twenty", "thirty", "forty", "fifty"};
//下面两行的写法是错的!!!
string a='adwdad';
string b[4] = {'twenty', 'thirty', 'sdfse', 'dawd'};
char s='a';
char s="a"; //这种写法是错的
char s[]="abcd"; //这个是字符串,以'\0'结束的,用strlen(s)是4,'\0'不计,但是它是有五个字符的
char s1[]={'a','b','c','d'}; //这个是字符数组,没有'\0',用strlen(s1)是不确定的,它有四个字符
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//获取字符串长度
int L =str.length();
int S =str.size(); //L=S 基本一样
//str去除元素
str.erase(pos,len); //str.erase(0,1); //删除pos开始的len个字符
str.erase(a,b); //删除迭代器a和b(不包含,左闭右开)标记范围内所有的元素,返回一个迭代器,指向被删除元素段后面的第一个元素
//string翻转
reverse(str.begin(),str.end());
//string插入元素
str.insert(pos,s2); //在下标为pos的元素之前插入string对象s2的副本
str.insert(pos,n,c); //在下标为pos的元素之前插入n个字符c
//子串
str.substr(pos,n); //返回一个string类型的字符串,它包含s中从下标pos开始的n个字符
str.substr(pos); //返回一个string类型的字符串,它包含从下标pos开始到s末为的所有字符
//查找
str.find(args); //在s中查找args的第一次出现
str.find(char c='a'); //在s中查找字符的第一次出现
str.find(string substr='abdb'); //在s中查找子字符串的第一次出现
str.rfind(args); //在s中查找args的最后一次出现
str.find_first_of(args); //在s中查args的任意字符的第一次出现
str.find_last_of(args); //在s中查找args的任意字符的最后一次出现
str.find_first_not_of(args); //在s中查找第一个不属于args的字符
str.find_last_not_of(args); //在s中查找最后一个不属于args的字符
//arg的各种情况:
c,pos //在s中,从下标pos标记的位置开始,查找字符c、pos的默认值是0
s2,pos //在s中,从下标pos标记的位置开始,查找string对象s2,pos的默认值为0
cp,pos //在s中从下标pos标记的位置开始,查找cp所指向的c风格的以空格结束的字符串。pos默认值为0
cp,pos,n //在s中,从下标pos标记的位置开始,查找指针cp所指向数组的前n个字符,pos和n都没有默认值
//返回的是元素下标:eg
size_t nLoc = str.find(args);
if (nLoc != string::npos) //未找到返回-1
cout << "nLoc is: " << nLoc << endl;
//比较compare
s.compare(s2); //比较s和s2
s.compare(pos1,n1,s2); //让s中从pos下标位置开始的n1个字符与s2做比较
s.compare(pos1,n1,s2,pos2,n2); //让s中从pos1下标位置开始的n1个字符与s2中从pos2下标位置开始的n2个字符做比较
s.compare(cp); //比较s和cp所指向的以空字符结束的字符串
s.compare(pos1,n1,cp); //让s从pos1下标位置开始的n1个字符与cp所指向的字符串做比较
s.compare(pos1,n1,cp,n2); //让s中从pos1下标位置开始的n1个字符与cp所指向字符串的前n2个字符做比较
//结果:
s1.compare(args);
//a.正数,此时s1大于args所代表的string对象
//b.负数,此时s1小于args所代表的string对象
//c.0,此时s1恰好等于args所代表的string对象
//追加append
s1 += s2;
s1.append(s2);
s1.append(s2, 5, 5); //取s2的第5个开始的5个字符追加给s1
s1.append(3, '!'); //s1尾部叠加3个!
1.2 vector
特性
- 底层为连续数组,顺序结构存储
- 自动扩容:自动内存管理,动态改变长度,随着元素的添加和删除而增大与缩小
- 找更大的空间
- 将所有的数据复制过去
- 释放原来的空间
- 增长策略由实现决定(通常 2 倍扩张)
- 可以快速随机访问任意位置元素O(1)
- 尾部添加:摊还 O(1)(扩容时为 O(n));尾部删除 O(1)
- 头部或中间插入和删除元素的复杂度为O(n),需要移动其他元素
定义与初始化
1
2
3
4
5
6
7
vector<int> Excel;
vector<int> Excel = {1,2,3,4};
vector<vector<int>> Excel = {{1,2},{1,2,3,4}};
vector<int> v(7,3); //指定值初始化,v被初始化为包含7个值为3的int
vector<int> v(5);
// 注意:这里是 iota 不是 itoa(需要头文件 <numeric>)
iota(v.begin(), v.end(), 2); //v初始化为{2,3,4,5,6}
1
2
vector<int> v(7, 3); // 初始化为 7个3
vector<vector<int>> v(7, vector<int>(5, 0)); // 初始化为 7行 5列 的0矩阵
常用代码块
1
2
3
4
5
6
7
8
//添加元素
Excel.push_back(nums);
vector<pair <int, string>> v;
v.emplace_back(3, "abc");
//去除元素
int nPosition = i;//元素脚标
Excel.erase(Excel.begin()+nPosition);
Excel.erase(Excel.end()-1); // 注意:最后一个元素是 Excel.end()-1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//--查找find
//查找的重载
class error
{
public:
string file_name;
int line;
int count;
/////vector 的find函数,当vector中为自定义类型时,需要重载==操作,如下语句
bool operator == (const error & obj) const //重载 "==" 操作符,函数最后的 const 别忘了,否则会报错。(详见:http://www.cnblogs.com/SZxiaochun/p/7731900.html)
{
return file_name == obj.file_name && line == obj.line; //具体匹配条件,自己设定
}
};
//迭代器查找
error error_in;
vector<error> errorlist;
//注意!!!!vector的find函数的使用方法不是v.find()!!!!
vector<error>::iterator result = find( errorlist.begin( ), errorlist.end( ), error_in );
if ( result == errorlist.end( ) ) //没找到
errorlist.push_back(error_in);
else //查找到
{
int nPosition = distance(errorlist.begin(), result);
errorlist[nPosition].count++;
}
//--排序sort
//排序
sort(Excel.begin(),Excel.end());
//自定义类型的排序重载
//方式1
class Words
{
public:
string strwords;
int letter[26]={ 0 };
///自定义类型时,需要重载<操作,如下语句
bool operator < (const Words & obj) const //重载 "<" 操作符,函数最后的 const 别忘了,否则会报错。(详见:http://www.cnblogs.com/SZxiaochun/p/7731900.html)
{
return strwords < obj.strwords; //具体匹配条件,自己设定
}
};
sort(Excel.begin(),Excel.end());
//方式2
//或者自定义函数
bool cmp(Words a,Words b)
{
if(a.zfc<b.zfc)
return true;
else if(a.zfc==b.zfc)
return a.a<b.a;
else
return false;
}
sort(Deriction.begin(),Deriction.end()); //注意::字典一定要排序
sort(Deriction.begin(),Deriction.end(),cmp);
//方式1
//其他方式
struct tree
{
string zfc;
int a;
//重载 "<" 操作符
bool operator<(const tree &a)
{
//自定义条件
if(zfc<a.zfc)
return true;
else if(zfc==a.zfc)
return this->a<a.a;
else
return false;
}
};
//方式2
//或者自定义函数
bool cmp(tree a,tree b)
{
if(a.zfc<b.zfc)
return true;
else if(a.zfc==b.zfc)
{
return a.a<b.a;
}
return false;
}
int main()
{
tree t[3];
t[0].zfc="hh";
t[0].a=1;
t[1].zfc="xx";
t[1].a=2;
t[2].zfc="hh";
t[2].a=0;
sort(t,t+3); //使用sort可以重载元素类型的比较符号
sort(t,t+3,cmp); //也可以自己写新的排序函数
}
// 使用swap清空容器
vector<int>().swap(vec); // 清空并释放内存
//最大元素
max_element() //返回范围内最大元素的迭代器
vector<int>::iterator max_e = max_element(candies.begin(), candies.end());
int max_num = *max_e;
1.3 deque (双端队列)
特性
- 头部或尾部添加:摊还 O(1);删除 O(1)
- 支持随机访问,可以访问任意位置元素,且比vector稍慢
- 中间插入和删除元素的复杂度为O(n),且比vector稍慢
- 原理:
- 很多个分段数组,每个分段数组的大小相同,每个小段是内存连续的
- 一个存放这些分段数组的首地址的索引数组
- 两端的插入效率非常高(摊还 O(1))
- 中间插入效率比较低 O(n)
- 支持随机访问 O(1)
创建
1
2
3
4
5
6
7
8
9
10
11
12
// 创建空deque
deque<int> dq;
// 使用初始化列表
deque<int> dq = {1, 2, 3, 4, 5};
// 指定大小和初始值
deque<int> dq(5, 10); // 5个元素,每个都是10
// 从其他容器复制
vector<int> vec = {1, 2, 3, 4, 5};
deque<int> dq(vec.begin(), vec.end());
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 访问元素
deque<int> dq = {1, 2, 3, 4, 5};
cout << dq[0] << endl; // 使用下标访问
cout << dq.at(1) << endl; // 使用at()访问,带边界检查
cout << dq.front() << endl; // 访问第一个元素
cout << dq.back() << endl; // 访问最后一个元素
// 添加元素
dq.push_front(0); // 在头部添加元素
dq.push_back(6); // 在尾部添加元素
dq.emplace_front(0); // 在头部构造并添加元素
dq.emplace_back(6); // 在尾部构造并添加元素
// 删除元素
dq.pop_front(); // 删除头部元素
dq.pop_back(); // 删除尾部元素
dq.erase(dq.begin() + 2); // 删除指定位置的元素
dq.erase(dq.begin(), dq.begin() + 2); // 删除一个范围的元素
// 大小操作
cout << dq.size() << endl; // 获取元素个数
cout << dq.empty() << endl; // 检查是否为空
dq.resize(10); // 调整大小
dq.resize(10, 0); // 调整大小并用0填充
迭代器操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用迭代器遍历
deque<int> dq = {1, 2, 3, 4, 5};
for (auto it = dq.begin(); it != dq.end(); ++it) {
cout << *it << " ";
}
// 使用反向迭代器
for (auto it = dq.rbegin(); it != dq.rend(); ++it) {
cout << *it << " ";
}
// 使用范围for循环
for (const auto& elem : dq) {
cout << elem << " ";
}
性能优化
1
2
3
4
5
6
7
8
9
// 预分配空间(注意:deque没有reserve方法)
// 但可以通过resize预分配空间
deque<int> dq;
dq.resize(1000); // 预分配1000个元素的空间
// 使用emplace代替push
deque<pair<int, string>> dq;
dq.emplace_back(1, "hello"); // 直接构造,避免拷贝
dq.emplace_front(0, "world"); // 在头部直接构造
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 1. 实现滑动窗口
void slidingWindow(const vector<int>& nums, int k) {
deque<int> dq; // 存储下标
for (int i = 0; i < nums.size(); ++i) {
// 移除超出窗口范围的元素
while (!dq.empty() && dq.front() <= i - k) {
dq.pop_front();
}
// 移除小于当前元素的元素
while (!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
dq.push_back(i);
if (i >= k - 1) {
cout << nums[dq.front()] << " "; // 输出窗口最大值
}
}
}
// 2. 实现双端队列
class MyCircularDeque {
private:
deque<int> dq;
int capacity;
public:
MyCircularDeque(int k) : capacity(k) {}
bool insertFront(int value) {
if (dq.size() >= capacity) return false;
dq.push_front(value);
return true;
}
bool insertLast(int value) {
if (dq.size() >= capacity) return false;
dq.push_back(value);
return true;
}
bool deleteFront() {
if (dq.empty()) return false;
dq.pop_front();
return true;
}
bool deleteLast() {
if (dq.empty()) return false;
dq.pop_back();
return true;
}
int getFront() {
return dq.empty() ? -1 : dq.front();
}
int getRear() {
return dq.empty() ? -1 : dq.back();
}
bool isEmpty() {
return dq.empty();
}
bool isFull() {
return dq.size() == capacity;
}
};
注意事项
- 内存管理
- deque的内存是分段的,不会像vector那样需要整体重新分配
- 但每个分段的大小是固定的,可能会浪费一些空间
- 迭代器失效
- 端部 push/pop:通常会使 所有迭代器 失效,但 既有元素的引用/指针 通常仍然有效(别依赖旧迭代器)
- 中间 insert/erase:会使 迭代器 以及 引用/指针 都可能失效(元素会被移动/重排)
- 性能考虑
- 如果需要频繁在两端操作,deque比vector更合适
- 如果需要频繁随机访问,vector比deque更合适
- 如果需要频繁在中间插入删除,list比deque更合适
1.4 list (双向链表)
特性
- 访问元素较慢O(n),不支持随机访问
- 任意位置插入和删除的时间固定O(1),而vector为O(n)
头文件
1
#include <list>
创建
1
2
3
4
5
6
7
list<int> c0; //空链表
//初始化
list<int> c0{1,2,3,4,5};
list<int> c1(3); //建一个含三个默认值是0的元素的链表
list<int> c2(5,2); //建一个含五个元素的链表,值都是2
list<int> c4(c2); //建一个c2的copy链表
list<int> c5(c1.begin(),c1.end()); ////c5含c1一个区域的元素[_First, _Last)。
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
c.begin(); //返回指向链表第一个元素的迭代器
c.end(); //返回指向链表最后一个元素之后的迭代器
c.rbegin() //返回逆向链表的第一个元素,即c链表的最后一个数据。
c.rend() //返回逆向链表的最后一个元素的下一个位置,即c链表的第一个数据再往前的位置。
c.front() //返回链表c的第一个元素。
c.back() //返回链表c的最后一个元素。
c.empty() //判断链表是否为空。
c.size() //返回链表c中实际元素的个数。
c.insert(pos,num) //在pos位置插入元素num。
c.insert(pos,n,num) //在pos位置插入n个元素num。
c.insert(pos,beg,end) //在pos位置插入区间为[beg,end)的元素。
c.erase(pos) //删除pos位置的元素。
c.push_back(num) //在末尾增加一个元素。
c.pop_back() //删除末尾的元素。
c.push_front(num) //在开始位置增加一个元素。
c.pop_front(); //删除第一个元素。
c.reverse(); //反转链表
c.unique(); //删除相邻的元素
c.sort(); //将链表排序,默认升序
c.sort(comp); //自定义回调函数实现自定义排序
1 list<int> a1{1,3,2,5,4};
2 a1.sort();
3 list<int>::iterator it;
4 cout << "sort():";
5 for(it = a1.begin();it!=a1.end();it++){
6 cout << *it << " ";
7 }
8 cout << endl;
9
10 a1.sort([](int n1,int n2){return n1>n2;});
11 cout << "sort(function point):";
12 for(it = a1.begin();it!=a1.end();it++){
13 cout << *it << " ";
14 }
15 cout << endl;
自定义一个链表
1
2
3
4
5
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
1.5 forward_list
todo(congyu)
1.6 set
特性
- 只有键
- 不允许重复元素
- 底层 红黑树
- 查找 复杂度 O(log n)
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用emplace代替insert
set<int> s;
s.emplace(1); // 直接构造,避免拷贝
// 使用extract(C++17)
auto node = s.extract(1); // 提取节点而不删除
if (!node.empty()) {
node.value() = 2; // 修改值
s.insert(std::move(node)); // 重新插入
}
// 使用merge(C++17)
set<int> s1{1, 2, 3};
set<int> s2{4, 5, 6};
s1.merge(s2); // 合并两个set
1.7 unordered_set
哈希集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//查找函数 find() 通过给定主键查找元素
unordered_set<int>::iterator find_iter = c1.find(1);
//value出现的次数 count() 返回匹配给定主键的元素的个数
c1.count(1);
//返回元素在哪个区域equal_range() 返回值匹配给定搜索值的元素组成的范围
pair<unordered_set<int>::iterator, unordered_set<int>::iterator> pair_equal_range = c1.equal_range(1);
//插入函数 emplace()
c1.emplace(1);
//插入函数 emplace_hint() 使用迭代器
c1.emplace_hint(ite_begin, 1);
//插入函数 insert()
c1.insert(1);
//删除 erase()
c1.erase(1);//1.迭代器 value 区域
//清空 clear()
c1.clear();
//交换 swap()
c1.swap(c2);
1.8 unordered_multiset
特性
- 允许重复元素
- 底层哈希表
- 查找复杂度:平均/摊还 O(1),最坏 O(n)
- 不保证顺序
- 支持快速查找、插入和删除
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 查找函数 find() 通过给定主键查找元素,返回第一个匹配的迭代器
unordered_multiset<int>::iterator find_iter = c1.find(1);
// value出现的次数 count() 返回匹配给定主键的元素的个数
size_t count = c1.count(1);
// 返回元素在哪个区域 equal_range() 返回值匹配给定搜索值的元素组成的范围
pair<unordered_multiset<int>::iterator, unordered_multiset<int>::iterator> pair_equal_range = c1.equal_range(1);
// 插入函数 emplace()
c1.emplace(1);
// 插入函数 emplace_hint() 使用迭代器
c1.emplace_hint(ite_begin, 1);
// 插入函数 insert()
c1.insert(1);
// 删除 erase()
c1.erase(1); // 1.迭代器 value 区域
// 清空 clear()
c1.clear();
// 交换 swap()
c1.swap(c2);
常用代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <unordered_set>
#include <iostream>
int main() {
unordered_multiset<int> ums = {1, 2, 2, 3, 3, 3};
// 插入元素
ums.insert(4);
ums.emplace(4);
// 查找元素
auto it = ums.find(2);
if (it != ums.end()) {
cout << "Found: " << *it << endl;
}
// 计数
cout << "Count of 3: " << ums.count(3) << endl;
// 范围
auto range = ums.equal_range(3);
for (auto i = range.first; i != range.second; ++i) {
cout << *i << " ";
}
cout << endl;
return 0;
}
1.9 map
特性
- 键值对
- 查找非常迅速 o(log n)
- 不支持随机访问(迭代器为双向迭代器,不能做 it+n 这类操作)
- 支持迭代器遍历
- 注意:map遍历的时候会自动根据key的大小排序!并不代表插入顺序!
- map可以自定义排序,但是只能以key排序
创建
1
2
map<typename1, typename2> mp;
map<string, int> mp;
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//访问元素
map<string, int> mp;
mp["apple"] = 15;
mp["bnanana"] = 16;
//方式一:直接访问
cout<<mp["bnanana"]<<endl;
//方式二:.at()访问,更安全
cout<<mp.at("bnanana")<<endl;
//方式三:通过迭代器访问
//map 可以使用 itr->first 来访问键, itr->second 来访问值。
for(map<char, int>::iterator itr = mp.begin(); itr != mp.end(); itr++)
cout<< itr -> first
<< itr -> second<<endl;
//查找
find()//返回迭代器
map<string, int>::iterator itr = mp.find("bnanana");
cout<< itr->first << itr->second<<endl;
//删除
erase()
map<string, int>::iterator itr = mp.find("bnanana");
mp.erase(itr); //删除该迭代器所指元素
mp.erase(mp.begin(), itr); //删除最开始到itr所指元素前的所有元素,前闭后开
mp.erase(itr, mp.end()); //删除 itr 及之后的所有元素(区间 [itr, end))
mp.erase(mp.begin()); //删除最开始的元素
mp.erase(--mp.end()); //删除最后面的元素
//清空
mp.clear();
//大小
mp.size();
常用代码块
自定义排序
- map的自定义排序只支持按照key排序
- 如果需要按照value排序,可以丢到vector里面,参考样例2。
样例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <map>
#include <string>
int main() {
// 自定义比较,只能以key排序
auto cmp = [](const std::string& a, const std::string& b){ return a.size() > b.size(); };
// 使用自定义比较函数创建 map
std::map<std::string, int, decltype(cmp)> customMap(cmp);
customMap["apple"] = 1;
customMap["banana"] = 2;
customMap["cherry"] = 3;
customMap["date"] = 4;
// 输出排序后的 map
for (const auto& pair : customMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
- 如果已经有一个map,需要进行排序,转到vector,然后使用sort
样例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
// 自定义比较函数
bool customCompare(const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) {
return a.second < b.second; // 按值排序
}
int main() {
std::map<std::string, int> myMap = {{"apple", 3}, {"banana", 1}, {"cherry", 2}};
// 将 map 的元素提取到 vector 中
std::vector<std::pair<std::string, int>> vec(myMap.begin(), myMap.end());
// 使用自定义比较函数对 vector 进行排序
std::sort(vec.begin(), vec.end(), customCompare);
// 输出排序后的结果
for (const auto& pair : vec) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
map 使用技巧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用emplace代替insert
map<string, int> m;
m.emplace("key", 1); // 直接构造,避免拷贝
// 使用try_emplace(C++17)
m.try_emplace("key", 1); // 只在key不存在时插入
// 使用insert_or_assign(C++17)
m.insert_or_assign("key", 2); // 插入或更新
// 使用结构化绑定(C++17)
for (const auto& [key, value] : m) {
cout << key << ": " << value << endl;
}
// 使用contains(C++20)
if (m.contains("key")) {
// key存在
}
1.10 unordered_map
特性
- 支持遍历
- 注意:内部哈希表,遍历顺序并不代表插入顺序
map: map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。 unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度通常为平均/摊还 O(1),最坏情况下可能退化到 O(n))。因此,其元素的排列顺序是无序的。
map: 优点: 有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作 红黑树,内部实现一个红黑树使得map的很多操作在 O(log n) 的时间复杂度下就可以实现,因此效率非常的高 缺点: 空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间 适用处:对于那些有顺序要求的问题,用map会更高效一些
unordered_map: 优点: 因为内部实现了哈希表,因此其查找速度通常很快(平均/摊还 O(1),最坏可能退化到 O(n)) 缺点: 哈希表的建立比较耗费时间 适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map
总结: 内存占有率的问题就转化成红黑树 VS hash表 , 还是unorder_map占用的内存要高。 但是unordered_map执行效率要比map高很多 对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的
unordered_map 使用技巧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 自定义哈希函数
struct MyHash {
size_t operator()(const string& s) const {
return hash<string>()(s);
}
};
unordered_map<string, int, MyHash> um;
// 自定义相等比较函数
struct MyEqual {
bool operator()(const string& a, const string& b) const {
return a.length() == b.length();
}
};
unordered_map<string, int, MyHash, MyEqual> um;
1.11 unordered_multimap
特性
- 键值对存储
- 允许重复键
- 底层哈希表
- 查找复杂度:平均/摊还 O(1),最坏 O(n)
- 不保证顺序
- 支持快速查找、插入和删除
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 访问元素
unordered_multimap<string, int> umm;
umm.emplace("apple", 1);
umm.emplace("banana", 2); // unordered_multimap 不支持 operator[]
// 查找函数 find() 通过给定主键查找元素,返回第一个匹配的迭代器
auto find_iter = umm.find("apple");
// value出现的次数 count() 返回匹配给定主键的元素的个数
size_t count = umm.count("apple");
// 返回元素在哪个区域 equal_range() 返回键匹配给定搜索值的元素组成的范围
auto pair_equal_range = umm.equal_range("apple");
// 插入函数 emplace()
umm.emplace("cherry", 3);
// 插入函数 emplace_hint() 使用迭代器
umm.emplace_hint(umm.begin(), "date", 4);
// 插入函数 insert()
umm.insert({"elderberry", 5});
// 删除 erase()
umm.erase("banana"); // 1.键 value 区域
// 清空 clear()
umm.clear();
// 交换 swap()
umm.swap(umm2);
常用代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <unordered_map>
#include <iostream>
int main() {
unordered_multimap<string, int> umm = {{"apple", 1}, {"banana", 2}, {"banana", 3}, {"cherry", 4}};
// 插入元素
umm.emplace("date", 5);
umm.insert({"elderberry", 6});
// 查找元素
auto it = umm.find("banana");
if (it != umm.end()) {
cout << "Found: " << it->first << " -> " << it->second << endl;
}
// 计数
cout << "Count of banana: " << umm.count("banana") << endl;
// 范围
auto range = umm.equal_range("banana");
for (auto i = range.first; i != range.second; ++i) {
cout << i->first << " -> " << i->second << endl;
}
return 0;
}
1.12 stack (栈)
特性
- 底层默认deque
- 只允许访问栈顶元素
- 先进后出
- 不允许随机访问栈元素
- 不允许遍历队列
创建
1
2
3
stack <int> stk;
//初始化
stack <int> stk = ;
1
2
3
4
5
stk.push(7); //入栈
stk.pop(); //出栈
stk.top(); //访问栈顶元素
stk.empty(); //判空
stk.size(); //元素个数
1.13 queue (队列)
特性
- 底层默认为deque
- 只允许访问队首和队尾元素
- 先进先出
- 不允许随机访问元素
- 不允许遍历队列
创建
1
2
3
queue <int> q;
//初始化
queue <int> q =;
1
2
3
4
5
6
q.push(7); //入队
q.pop(); //出队列
q.front(); //访问队首元素
q.back(); //访问队尾元素
q.empty(); //判空
q.size(); //返回队中元素个数
1.14 priority_queue (优先队列)
特性
- 底层默认为vector
- priority_queue支持的操作与queue一样
- priority_queue 支持元素优先级排序,可以自定义优先级大小的函数greater()
- pop时优先pop优先级高的元素
- 默认顺序是top最大
常用代码块
降序队列 (默认,top最大)
1
2
//降序队列 // top 最大
priority_queue <int,vector<int>,less<int>> queue_a;
升序队列
1
2
//升序队列 // top 最小
priority_queue <int,vector<int>,greater<int> > queue_a;
自定义顺序队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//自定义排序方式
struct Node{
int x,y;
Node(int a = 0,int b = 0):x(a),y(b){}
};
auto cmp = [](const Node& a,
const Node& b) {
if(a.x == b.x){
return a.y > b.y;
}
return a.x > b.x; // x越大的越排在后面
};
std::priority_queue<Node, std::vector<Node>, decltype(cmp)> pq(cmp);
优先队列举例:
1
2
3
4
5
6
7
8
9
10
11
12
int findKthLargest(std::vector<int>& nums, int k) {
std::priority_queue<int, std::vector<int>, std::greater<int> > q;
for (auto &num : nums){
if (q.size() < k) {
q.push(num);
} else if (num > q.top()) {
q.pop();
q.push(num);
}
}
return q.top();
}
2 算法
头文件
1
#include <algorithm>
2.1 算法总结
1.遍历与访问
- for_each:对区间内每个元素执行操作
- for_each_n (C++17):对前n个元素执行操作
- count, count_if:统计元素出现次数
- all_of, any_of, none_of:条件检查
2.排序与排列
- sort:排序
- stable_sort:稳定排序
- partial_sort:部分排序
- nth_element:选取第n大元素
- is_sorted, is_sorted_until:检查是否已排序
- reverse:反转
- reverse_copy:反转并复制
- shuffle:随机打乱
- rotate:旋转
- rotate_copy:旋转并复制
- partition:分区
- stable_partition:稳定分区
- partition_point:查找分区点
- is_partitioned:检查是否已分区
- partition_copy:分区并复制
- equal:比较两个序列是否相等
- lexicographical_compare:字典序比较
3.查找
- find, find_if, find_if_not:查找元素
- find_first_of:查找第一个匹配的元素
- find_end:查找最后一个匹配的子序列
- search:查找子序列
- search_n:查找连续n个相同元素
- adjacent_find:查找相邻的重复元素
- mismatch:查找第一个不匹配的位置
- binary_search:二分查找
- lower_bound, upper_bound:查找区间边界
- equal_range:查找等于某值的区间
4.修改操作
- copy, copy_if:复制
- copy_n:复制n个元素
- copy_backward:向后复制
- move:移动
- move_backward:向后移动
- swap, iter_swap:交换
- replace, replace_if:替换
- replace_copy, replace_copy_if:替换并复制
- fill, fill_n:填充
- remove, remove_if:移除(不真正删除元素)
- remove_copy, remove_copy_if:移除并复制
- unique:去重(不真正删除元素)
- unique_copy:去重并复制
- generate:生成
- generate_n:生成n个元素
- transform:元素变换
- sample (C++17):采样
5.合并与集合操作
- merge:合并有序区间
- inplace_merge:原地合并
- set_union, set_intersection, set_difference, set_symmetric_difference:集合操作
- includes:检查一个序列是否包含另一个序列
6.数值算法
- accumulate:区间求和
- partial_sum:部分求和
- adjacent_difference:相邻差
- inner_product:内积
- iota:生成递增序列
- min, max:最小/最大值
- min_element, max_element:查找最小/最大元素
- gcd, lcm (C++17):最大公约数、最小公倍数
- next_permutation, prev_permutation:全排列
- is_permutation:检查是否为排列
7.堆操作
- make_heap:建堆
- push_heap:入堆
- pop_heap:出堆
- sort_heap:堆排序
- is_heap, is_heap_until:检查是否为堆
2.2 常用算法详解
遍历与访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// for_each: 对区间内每个元素执行操作
//todo
// count, count_if:统计元素出现次数
// count: 计数
int n = count(vec.begin(), vec.end(), value);
// count_if: 条件计数
int n = count_if(vec.begin(), vec.end(), [](int x) { return x > 5; });
// all_of/any_of/none_of: 条件检查
bool all_positive = all_of(vec.begin(), vec.end(), [](int x) { return x > 0; });
bool any_positive = any_of(vec.begin(), vec.end(), [](int x) { return x > 0; });
bool none_positive = none_of(vec.begin(), vec.end(), [](int x) { return x > 0; });
排序与排列
1
2
3
4
5
6
7
8
9
10
11
// sort: 排序
sort(vec.begin(), vec.end());
// stable_sort: 稳定排序
stable_sort(vec.begin(), vec.end());
// partial_sort: 部分排序
partial_sort(vec.begin(), vec.begin() + 5, vec.end()); // 只排序前5个元素
// nth_element: 将第n个元素放到正确位置
nth_element(vec.begin(), vec.begin() + n, vec.end());
自定义排序规则
1
2
3
4
5
// 条件满足时a排b前面
auto cmp = [](const auto&a, const auto&b) {
return a.level > b.level;
};
std::sort(v.begin(), v.end(), cmp);
翻转容器 reverse
1
std::reverse(v.begin(),v.end());
查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// find: 查找元素
auto it = find(vec.begin(), vec.end(), value);
if (std::find(myvec.begin(), myvec.end(), num) == myvec.end()) {
// 没找到
}
// find_if: 使用谓词查找元素
auto it = find_if(vec.begin(), vec.end(), [](int x) { return x > 5; });
// binary_search: 二分查找(要求容器已排序)
bool exists = binary_search(vec.begin(), vec.end(), value);
// 二分查找
// lower_bound: 返回第一个不小于value的元素位置
int value = 5;
auto it = lower_bound(vec.begin(), vec.end(), value);
if (it != vec.end()) {
cout << "Found 5 at position " << (it - vec.begin()) << endl;
}
// upper_bound: 返回第一个大于value的元素位置
auto it = upper_bound(vec.begin(), vec.end(), value);
修改操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// copy: 复制元素
copy(source.begin(), source.end(), dest.begin());
// move: 移动元素
move(source.begin(), source.end(), dest.begin());
// replace: 替换元素
replace(vec.begin(), vec.end(), old_value, new_value);
// fill: 填充元素
fill(vec.begin(), vec.end(), value);
// generate: 生成元素
generate(vec.begin(), vec.end(), []() { return rand(); });
// transform: 转换元素
transform(vec.begin(), vec.end(), result.begin(), [](int x) { return x * 2; });
删除
1
2
3
4
5
6
7
8
9
10
11
12
// remove
// 移除元素(不改变容器大小)
auto new_end = remove(vec.begin(), vec.end(), value);
vec.erase(new_end, vec.end()); // 实际删除元素
// unique
// 移除相邻重复元素
//unique将相邻的重复元素移动到末尾,并不是直接删除,
//注意"相邻的",所以要先进行排序,保证重复元素相邻
//unique() 的返回值就是被后置元素的"首地址"(迭代器)
auto new_end = unique(vec.begin(), vec.end());
vec.erase(new_end, vec.end()); // 实际删除元素
数值算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// accumulate: 累加
int sum = accumulate(vec.begin(), vec.end(), 0);
int product = accumulate(vec.begin(), vec.end(), 1, multiplies<int>());
// max_element/min_element: 找最大/最小元素
auto max_it = max_element(vec.begin(), vec.end());
auto min_it = min_element(vec.begin(), vec.end());
// inner_product: 内积
int result = inner_product(vec1.begin(), vec1.end(), vec2.begin(), 0);
// partial_sum: 部分和
partial_sum(vec.begin(), vec.end(), result.begin());
// adjacent_difference: 相邻差
adjacent_difference(vec.begin(), vec.end(), result.begin());
2.3 算法复杂度
- 排序算法
- sort: O(n log n)
- stable_sort: O(n log n)
- partial_sort: O(n log k),k为部分排序的元素个数
- nth_element: O(n)
- 查找算法
- find: O(n)
- binary_search: O(log n)
- lower_bound/upper_bound: O(log n)
- 修改算法
- copy: O(n)
- transform: O(n)
- replace: O(n)
- fill: O(n)
2.4 算法稳定性
- 稳定算法
- stable_sort: 保持相等元素的相对顺序
- stable_partition: 保持相等元素的相对顺序
- merge: 保持相等元素的相对顺序
- 不稳定算法
- sort: 不保证相等元素的相对顺序
- partition: 不保证相等元素的相对顺序
- nth_element: 不保证相等元素的相对顺序
2.5 注意事项
- 迭代器范围
- 确保迭代器范围有效
- 注意左闭右开区间 [first, last)
- 检查迭代器是否指向同一个容器
- 谓词函数
- 确保谓词函数满足严格弱序
- 避免在谓词函数中修改元素
- 注意谓词函数的性能影响
3 容器迭代器
- 基本迭代器类型
- 输入迭代器
- 输出迭代器
- 前向迭代器
- 双向迭代器
- 随机访问迭代器
- 特殊迭代器
- 反向迭代器
- 常量迭代器
- 移动迭代器
- 迭代器适配器
- 插入迭代器
- 流迭代器
- 反向迭代器适配器
- 迭代器辅助函数
- std::advance
- std::distance
- std::next/prev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 双向迭代器
std::list<int>::iterator list_it;
std::set<int>::iterator set_it;
// 反向迭代器
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int>::reverse_iterator rit = v.rbegin();
// 常量迭代器
std::vector<int> v = {1, 2, 3};
std::vector<int>::const_iterator cit = v.cbegin();
// *cit = 10; // 错误:不能修改值
// 移动迭代器
std::vector<std::string> source = {"hello", "world"};
std::vector<std::string> dest(std::make_move_iterator(source.begin()),
std::make_move_iterator(source.end()));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 插入迭代器
std::back_inserter(container); // 尾部插入
std::front_inserter(container); // 头部插入
std::inserter(container, pos); // 指定位置插入
// 流迭代器
// 输入流迭代器
std::istream_iterator<int> input_iter(std::cin);
// 输出流迭代器
std::ostream_iterator<int> output_iter(std::cout, " ");
// 反向迭代器适配器
std::vector<int> v = {1, 2, 3};
auto rit = std::make_reverse_iterator(v.end());
1
2
3
4
5
6
7
8
std::list<int>::iterator it = lst.begin();
std::advance(it, 5); // 前进5个位置
auto dist = std::distance(begin, end); // 计算迭代器间距离
auto next_it = std::next(it); // 下一个位置
auto prev_it = std::prev(it); // 前一个位置
auto next_5 = std::next(it, 5); // 前进5个位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用auto简化迭代器声明
for (auto it = vec.begin(); it != vec.end(); ++it) {
// 使用迭代器
}
// 使用范围for循环
for (const auto& element : vec) {
// 使用元素
}
// 使用反向迭代器
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
// 反向遍历
}
// 使用const迭代器
for (const auto it = vec.cbegin(); it != vec.cend(); ++it) {
// 只读访问
}
4 其他
emplace原理
emplace 的原理:
- 直接在容器内部原地构造对象,避免了临时对象的创建和拷贝,提高了效率。
- 它通过完美转发(forwarding)参数,将参数直接传递给元素类型的构造函数。
以 C++11 及以上标准为例,emplace 的实现通常利用了右值引用和 std::forward:
1
2
3
4
5
template <class... Args>
iterator emplace(Args&&... args) {
// 在容器内部分配空间,并用参数 args... 原地构造对象
return insert_internal(std::forward<Args>(args)...);
}
核心点:
- 支持可变参数模板(template
)。 - 使用 std::forward 完美转发参数,保留参数的左值/右值属性。
- 直接在容器的内存空间构造对象,减少不必要的拷贝或移动。
std::sort原理
- 内省排序的混合算法策略
- 结合了三种不同的排序方法:
- 快速排序(Quicksort) - 主要算法
- 堆排序(Heapsort) - 最坏情况保证
- 插入排序(Insertion Sort) - 小数组优化
详细实现策略:
- 递归深度限制
1 2
// 递归深度限制,防止快排退化 depth_limit = 2 * log2(n)
- 算法选择策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
void sort(Iterator first, Iterator last) { if (last - first > threshold) { // threshold通常为16 if (depth_limit > 0) { // 使用快速排序 quicksort(first, last); depth_limit--; } else { // 递归过深,使用堆排序 heapsort(first, last); } } else { // 数组较小,使用插入排序 insertion_sort(first, last); } }
三种算法的作用:
- 快速排序(主体算法)
- 作用:处理大部分情况,平均性能最优
- 复杂度:平均 O(n log n),最坏 O(n²)
- 特点:分治策略,就地排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 快排的三路分割优化
template<typename Iterator>
void quicksort(Iterator first, Iterator last) {
if (first >= last) return;
// 选择pivot(通常使用三数取中法)
auto pivot = median_of_three(first, first + (last - first)/2, last - 1);
// 分割(C++14 不支持结构化绑定,这里用 pair 接收)
auto range = partition(first, last, pivot);
auto lt = range.first;
auto gt = range.second;
// 递归排序
quicksort(first, lt);
quicksort(gt, last);
}
- 堆排序(保底策略)
- 作用:当递归深度过深时启用,保证最坏情况性能
- 复杂度:O(n log n),最坏情况也是 O(n log n)
- 特点:不会退化,但常数因子较大
1
2
3
4
5
6
7
8
9
10
template<typename Iterator>
void heapsort(Iterator first, Iterator last) {
// 建堆
make_heap(first, last);
// 依次取出最大元素
while (last > first) {
pop_heap(first, last--);
}
}
- 插入排序(小数组优化)
- 作用:处理小数组(通常 ≤ 16个元素)
- 复杂度:O(n²),但在小数组上常数很小
- 特点:简单高效,对近似有序数组性能很好
1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename Iterator>
void insertion_sort(Iterator first, Iterator last) {
for (auto it = first + 1; it != last; ++it) {
auto key = *it;
auto j = it - 1;
while (j >= first && *j > key) {
*(j + 1) = *j;
--j;
}
*(j + 1) = key;
}
}
关键优化技术:
- 三数取中(Median-of-Three)
1 2 3 4 5 6 7 8 9 10 11 12
template<typename Iterator> Iterator median_of_three(Iterator a, Iterator b, Iterator c) { if (*a < *b) { if (*b < *c) return b; // a < b < c if (*a < *c) return c; // a < c <= b return a; // c <= a < b } else { if (*a < *c) return a; // b <= a < c if (*b < *c) return c; // b < c <= a return b; // c <= b <= a } }
- 三路分割(3-way Partition)
- 将数组分为:< pivot、= pivot、> pivot 三部分
- 有效处理重复元素多的情况
- 阈值优化
1 2
const int INSERTION_SORT_THRESHOLD = 16; // 小数组阈值 const int DEPTH_LIMIT_FACTOR = 2; // 递归深度因子
性能分析
| 算法组成 | 时间复杂度 | 空间复杂度 | 使用场景 |
|---|---|---|---|
| 快速排序 | 平均 O(n log n) | O(log n) | 大数组主体 |
| 堆排序 | O(n log n) | O(1) | 递归过深时 |
| 插入排序 | O(n²) | O(1) | 小数组 |
| 整体 | O(n log n) | O(log n) | 所有情况 |
算法特性
- 时间复杂度:
- 平均情况:O(n log n)
- 最坏情况:O(n log n)(由于堆排序保底)
- 最好情况:O(n log n)
-
空间复杂度:O(log n)(递归栈空间)
- 稳定性:不稳定
- 如需稳定排序,使用
stable_sort
- 如需稳定排序,使用
- 适用性:
- 要求随机访问迭代器
- 适用于各种数据规模
- 对各种数据分布都有良好性能
与其他排序算法的比较
| 算法 | 平均复杂度 | 最坏复杂度 | 稳定性 | 特点 |
|---|---|---|---|---|
| std::sort | O(n log n) | O(n log n) | 不稳定 | 综合性能最优 |
| stable_sort | O(n log n) | O(n log²n)* | 稳定 | 保持相对顺序 |
| partial_sort | O(n log k) | O(n log k) | 不稳定 | 部分排序 |
| nth_element | O(n) | O(n²) | 不稳定 | 选择第k小元素 |
* stable_sort 的最坏复杂度与实现/可用额外内存有关:额外内存充足时通常是 O(n log n),内存受限时可能退化到 O(n log² n)。
总结
std::sort通过内省排序的混合策略,在各种情况下都能提供优秀的性能:
- 快速排序提供了最佳的平均性能
- 堆排序确保了最坏情况的性能保证
- 插入排序优化了小数组的处理效率
这种设计使得std::sort成为了实际应用中最可靠和高效的通用排序算法。
迭代器失效
vector的迭代器失效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::vector<int> vec{1, 2, 3, 4, 5};
auto it = vec.begin();
// ❌ 插入导致扩容,迭代器失效
vec.push_back(6); // 可能扩容
std::cout << *it; // 未定义行为!
// ✅ 重新获取迭代器
vec.push_back(6);
it = vec.begin();
std::cout << *it; // OK
// ✅ 使用索引代替迭代器
size_t index = 0;
vec.push_back(6);
std::cout << vec[index]; // OK
删除元素时的迭代器失效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
std::vector<int> vec{1, 2, 3, 4, 5};
// ❌ 错误:删除后继续使用迭代器
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it % 2 == 0) {
vec.erase(it); // it失效!
}
}
// ✅ 正确:使用erase的返回值
for (auto it = vec.begin(); it != vec.end(); ) {
if (*it % 2 == 0) {
it = vec.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
// ✅ 更好:使用std::remove_if(C++20前)
vec.erase(
std::remove_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; }),
vec.end()
);
// ✅ 最佳:std::erase_if(C++20)
std::erase_if(vec, [](int x) { return x % 2 == 0; });
不同容器的失效规则:
| 容器 | 插入(迭代器) | 插入(引用/指针) | 删除(迭代器) | 删除(引用/指针) | 备注 |
|---|---|---|---|---|---|
| vector | 若扩容:全部失效;不扩容:插入点及之后失效(含 end()) | 同左 | 删除点及之后失效(含 end()) | 被移动/覆盖位置及之后的引用/指针失效 | 关键点在“是否触发重分配” |
| deque | 端部 push/pop:迭代器失效;中间 insert/erase:通常全部失效 | 端部 push/pop:既有元素引用/指针通常有效;中间 insert/erase:可能失效 | erase:迭代器失效(常见实现为全部失效) | 被删元素引用/指针失效;中间 erase 可能影响其它元素 | 标准保证迭代器失效,别依赖旧迭代器 |
| list / forward_list | 不失效 | 不失效 | 仅被删元素迭代器失效 | 仅被删元素引用/指针失效 | 节点链表,插删不移动其它元素 |
| map/set | 不失效 | 不失效 | 仅被删元素迭代器失效 | 仅被删元素引用/指针失效 | 红黑树节点稳定 |
| unordered_map/set | 若触发 rehash:全部迭代器失效;否则通常不失效 | rehash 不会移动节点,既有元素引用/指针通常仍有效 | 仅被删元素迭代器失效;rehash 会使全部迭代器失效 | 仅被删元素引用/指针失效 | insert 可能触发 rehash(负载因子/rehash) |