STL基础知识总结

STL概述,包括容器,算法,容器迭代器等STL基础内容

Posted by CongYu on January 1, 2020

STL

1 容器

    1. 序列容器
      • string
      • vector 动态数组
      • deque 双端队列
      • list 双向链表
      • forward_list 单向链表
    1. 关联容器
      • set
      • unordered_set
      • unordered_multiset
      • map
      • unordered_map
      • unordered_multimap
    1. 容器适配器
      • 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_back
    • deque:支持两端操作 push_front/back, pop_front/back
    • list:支持两端操作,额外提供 merge, sort, reverse, unique
    • forward_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)

  • 受限接口:不提供迭代器,只提供特定的访问和修改操作
  • stacktop, push, pop
  • queuefront, back, push, pop
  • priority_queuetop, 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;
    }
};

注意事项

  1. 内存管理
    • deque的内存是分段的,不会像vector那样需要整体重新分配
    • 但每个分段的大小是固定的,可能会浪费一些空间
  2. 迭代器失效
    • 端部 push/pop:通常会使 所有迭代器 失效,但 既有元素的引用/指针 通常仍然有效(别依赖旧迭代器)
    • 中间 insert/erase:会使 迭代器 以及 引用/指针 都可能失效(元素会被移动/重排)
  3. 性能考虑
    • 如果需要频繁在两端操作,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 算法复杂度

  1. 排序算法
    • sort: O(n log n)
    • stable_sort: O(n log n)
    • partial_sort: O(n log k),k为部分排序的元素个数
    • nth_element: O(n)
  2. 查找算法
    • find: O(n)
    • binary_search: O(log n)
    • lower_bound/upper_bound: O(log n)
  3. 修改算法
    • copy: O(n)
    • transform: O(n)
    • replace: O(n)
    • fill: O(n)

2.4 算法稳定性

  1. 稳定算法
    • stable_sort: 保持相等元素的相对顺序
    • stable_partition: 保持相等元素的相对顺序
    • merge: 保持相等元素的相对顺序
  2. 不稳定算法
    • sort: 不保证相等元素的相对顺序
    • partition: 不保证相等元素的相对顺序
    • nth_element: 不保证相等元素的相对顺序

2.5 注意事项

  1. 迭代器范围
    • 确保迭代器范围有效
    • 注意左闭右开区间 [first, last)
    • 检查迭代器是否指向同一个容器
  2. 谓词函数
    • 确保谓词函数满足严格弱序
    • 避免在谓词函数中修改元素
    • 注意谓词函数的性能影响

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原理
  • 内省排序的混合算法策略
  • 结合了三种不同的排序方法:
  1. 快速排序(Quicksort) - 主要算法
  2. 堆排序(Heapsort) - 最坏情况保证
  3. 插入排序(Insertion Sort) - 小数组优化

详细实现策略:

  1. 递归深度限制
    1
    2
    
    // 递归深度限制,防止快排退化
    depth_limit = 2 * log2(n)
    
  2. 算法选择策略
    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);
     }
    }
    

三种算法的作用:

  1. 快速排序(主体算法)
    • 作用:处理大部分情况,平均性能最优
    • 复杂度:平均 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);
}
  1. 堆排序(保底策略)
    • 作用:当递归深度过深时启用,保证最坏情况性能
    • 复杂度: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--);
    }
}
  1. 插入排序(小数组优化)
    • 作用:处理小数组(通常 ≤ 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;
    }
}

关键优化技术:

  1. 三数取中(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
     }
    }
    
  2. 三路分割(3-way Partition)
    • 将数组分为:< pivot、= pivot、> pivot 三部分
    • 有效处理重复元素多的情况
  3. 阈值优化
    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) 所有情况

算法特性

  1. 时间复杂度
    • 平均情况:O(n log n)
    • 最坏情况:O(n log n)(由于堆排序保底)
    • 最好情况:O(n log n)
  2. 空间复杂度:O(log n)(递归栈空间)

  3. 稳定性:不稳定
    • 如需稳定排序,使用 stable_sort
  4. 适用性
    • 要求随机访问迭代器
    • 适用于各种数据规模
    • 对各种数据分布都有良好性能

与其他排序算法的比较

算法 平均复杂度 最坏复杂度 稳定性 特点
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)