C++标准模板库(STL)
C++标准模板库(STL)
参考来源:知乎:【C++】标准模板库(STL):超快入门!算法竞赛必看!
2023.10.22@isSeymour
零、废话说在前头
0.1 什么是C++标准模板库(STL)?
-
标准模板库 STL(Standard Template Library),是 C++ 标准库的一部分,不需要单独安装,只需要
#include
头文件。 -
C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。
-
C++ 语言的核心优势之一就是便于软件的复用。
C++ 语言有两个方面体现了复用:
- 面向对象的继承和多态机制
- 通过模板的概念实现了对泛型程序设计的支持
-
C++中的模板,就好比英语作文的模板,只换主题,不换句式和结构。对应到C++模板,就是只换类型,不换方法。
0.2 STL有什么优势?
-
STL封装了很多实用的容器,省时省力,能够让你将更多心思放到解决问题的步骤上,而非费力去实现数据结构诸多细节上,像极了用python时候的酣畅淋漓。
P.S. 如果你对STL源码颇有兴趣,那你不妨拜读C++大师侯捷的杰作《STL源码剖析》。
0.3 STL六大部件
- 容器(Containers)
- 分配器(Allocators)
- 算法(Algorithm)
- 迭代器(Iterators)
- 适配器(Adapters)
- 仿函数(Functors)
要真正提高C++编程效率,需要将STL六大部件结合使用,才能大放异彩。所谓部件,也即是零件,需要将这六大零件组装在一起,配合使用,整整齐齐。
STL的基本使用:
vector
set
string
map
queue
priority_queue
stack
pair
algorithm
第一部分 数据结构
一、vector
vector(矢量),是一种「变长数组」,即“自动改变数组长度的数组”。
注意:在局部区域中(比如局部函数里面)开vector数组,是在堆空间里面开的。
在局部区域开数组是在栈空间开的,而栈空间比较小,如果开了非常长的数组就会发生爆栈。
故局部区域不可以开大长度数组,但是可以开大长度
vector
。
1.1 初始化
要使用vector,需要添加头文件:
1 |
|
(1)一维初始化
像定义变量一样定义vector变量。类型名可以是int、double、char、struct,也可以是STL容器:vector、set、queue。
1 | vector<类型名> 变量名; |
(2)二维初始化
1 | // 定义第一维固定长度为5,第二维可变化的二维数组。 |
1 | // 行列均可变 |
1 | // 行列长度均固定 n + 1行 m + 1列初始值为0 |
1.2 方法函数
(1)函数清单
c指定为数组名称,含义中会注明算法复杂度。
方法函数 | 含义 | 时间复杂度 |
---|---|---|
c.front() |
返回第一个数据 | O(1) |
c.back() |
返回数组中的最后一个数据 | O(1) |
c.pop_back() |
删除最后一个数据 | O(1) |
c.push_back(element) |
在尾部加一个数据 | O(1) |
c.size() |
返回实际数据个数(unsigned类型) | O(1) |
c.clear() |
清空 | O(N),N为元素个数 |
c.resize(n, v) |
改变数组大小为n ,n 个空间数值赋为v ,如果没有默认赋值为0 |
|
c.insert(it, x) |
向任意迭代器it 插入一个元素x |
O(N) |
c.erase(__position) |
删除一个元素 | |
c.erase(first,last) |
删除[first,last) 的所有元素 |
O(N) |
c.begin() |
返回首元素的迭代器(通俗来说就是地址) | O(1) |
c.end() |
返回最后一个元素后一个位置的迭代器(地址) | O(1) |
c.empty() |
判断是否为空,为空返回真,反之返回假 | O(1) |
注意:
end()
返回的是最后一个元素的后一个位置的地址,不是最后一个元素的地址,所有STL容器均是如此删除一个的使用示例
v.erase(v.begin()+3);
删除v[3]
(2)排序
1 | sort(c.begin(), c.end()); |
1.3 访问
(1)下标
一维数组的下标是从0到v.size()−1
,访问之外的数会出现越界错误
1 | //添加元素 |
(2)迭代器
类似指针一样的访问 ,首先需要声明迭代器变量,和声明指针变量一样
1 | //迭代器访问 |
注意:
vi[i]
和*(vi.begin() + i)
等价vector
和string
的STL
容器支持*(it + i)
的元素访问,其它容器可能也可以支持这种方式访问,但用的不多,可自行尝试
1.4 常见用途
(1)储存数据
vector本身可以作为数组使用,而且在一些元素个数不确定的场合可以很好地节省空间。
(2)用邻接表存储图
使用vector实现邻接表,更为简单。
二、stack
栈为数据结构的一种,是STL中实现的一个先进后出,后进先出的容器。
2.1 初始化
1 | //头文件需要添加 |
2.2 方法函数
(1)函数清单
方法函数 | 含义 | 时间复杂度 |
---|---|---|
s.push(ele) |
元素ele 入栈 |
O(1) |
s.pop() |
移除栈顶元素 | O(1) |
s.top() |
取得栈顶元素(但不删除) | O(1) |
s.empty() |
检测栈内是否为空,空为真 | O(1) |
s.size() |
返回栈内元素的个数 | O(1) |
2.3 栈遍历
栈遍历
- 栈只能对栈顶元素进行操作,如果想要进行遍历,只能将栈中元素一个个取出来存在数组中
数组模拟栈进行遍历
- 通过一个数组对栈进行模拟,一个存放下标的变量
top
模拟指向栈顶的指针。 - 特点: 比
STL
的stack
速度更快,遍历元素方便
1 | int s[100]; // 栈 从左至右为栈底到栈顶 |
三、queue
队列是一种先进先出的数据结构。
3.1 初始化
1 | //头文件 |
3.2 方法函数
方法函数 | 含义 | 时间复杂度 |
---|---|---|
q.front() |
返回队首元素 | O(1) |
q.back() |
返回队尾元素 | O(1) |
q.push(element) |
尾部添加一个元素element 进队 |
O(1) |
q.pop() |
删除第一个元素 出队 | O(1) |
q.size() |
返回队列中元素个数,返回值类型unsigned int |
O(1) |
q.empty() |
判断是否为空,队列为空,返回true |
O(1) |
3.3 队列模拟
使用q[]
数组模拟队列
hh
表示队首元素的下标,初始值为0
tt
表示队尾元素的下标,初始值为-1
,表示刚开始队列为空
1 |
|
四、deque
首尾都可插入和删除的队列为双端队列。
4.1 初始化
1 | //添加头文件 |
4.2 方法函数
方法函数 | 含义 | 时间复杂度 |
---|---|---|
push_back(x)/push_front(x) |
把x 插入队尾后 / 队首 |
O(1) |
back()/front() |
返回队尾 / 队首元素 | O(1) |
pop_back() / pop_front() |
删除队尾 / 队首元素 | O(1) |
erase(iterator it) |
删除双端队列中的某一个元素 | |
erase(iterator first,iterator last) |
删除双端队列中[first,last) 中的元素 |
|
empty() |
判断deque是否空 | O(1) |
size() |
返回deque的元素数量 | O(1) |
clear() |
清空deque | O(1) |
4.3 排序
deque可以进行排序
1 | //从小到大 |
五、priority_queue
优先队列是在正常队列的基础上加了优先级,保证每次的队首元素都是优先级最大的。
可以实现每次从优先队列中取出的元素都是队列中优先级最大的一个。
它的底层是通过堆来实现的。
5.1 初始化
1 | //头文件 |
5.2 函数方法
方法函数 | 含义 | 时间复杂度 |
---|---|---|
q.top() |
访问队首元素 | |
q.push() |
入队 | |
q.pop() |
堆顶(队首)元素出队 | |
q.size() |
队列元素个数 | |
q.empty() |
是否为空 | |
empty() |
判断deque是否空 |
- 注意没有
clear()
!- 优先队列只能通过
top()
访问队首元素(优先级最高的元素)
5.3 设置优先级
5.3.1 基本数据类型的优先级
1 | priority_queue<int> pq; // 默认大根堆, 即每次取出的元素是队列中的最大值 |
参数解释:
- 第二个参数:
vector< int >
是用来承载底层数据结构堆的容器,若优先队列中存放的是double
型数据,就要填vector< double >
总之存的是什么类型的数据,就相应的填写对应类型。同时也要改动第三个参数里面的对应类型。 - 第三个参数:
less< int >
表示数字大的优先级大,堆顶为最大的数字
greater< int >
表示数字小的优先级大,堆顶为最小的数字
int代表的是数据类型,也要填优先队列中存储的数据类型
1. 基础写法(非常常用)
1 | priority_queue<int> q1; // 默认大根堆, 即每次取出的元素是队列中的最大值 |
2. 自定义排序(不常见,主要是写着麻烦)
1 | struct cmp1 { |
5.3.2 结构体优先级设置
优先队列中存储结构体类型,必须要设置优先级,即结构体的比较运算(因为优先队列的堆中要比较大小,才能将对应最大或者最小元素移到堆顶)。
- 优先级设置可以定义在结构体内进行小于号重载,也可以定义在结构体外。
1 | //要排序的结构体(存储在优先队列里面的) |
- 版本一:自定义全局比较规则
1 | //定义的比较结构体 |
-
版本二:直接在结构体里面写
因为是在结构体内部自定义的规则,一旦需要比较结构体,自动调用结构体内部重载运算符规则。
1 | // 方式一 |
1 | // 方式二 |
注意: 优先队列自定义排序规则和
sort()
函数定义cmp
函数很相似,但是最后返回的情况是相反的。即相同的符号,最后定义的排列顺序是完全相反的。
所以只需要记住sort
的排序规则和优先队列的排序规则是相反的就可以了。
5.4 存储特殊类型的优先级
存储pair类型
- 排序规则:
默认先对pair
的first
进行降序排序,然后再对second
降序排序
对first
先排序,大的排在前面,如果first
元素相同,再对second
元素排序,保持大的在前面。
1 |
|
六、map
未完待续…
第二部分 算法
1.排序sort
未完待续…