【C++ STL竞赛用】
**C++ STL(Standard Template Library)**标准模板库:包含一些常用数据结构与算法的模板的C++软件库。包含四个组件:算法(Algorithms)、容器(Container)、仿函数(Functors)、迭代器(Iterators)。
示例
- 算法:
sort(a.begin(),a.end())
- 容器:
priority_queue <int > pque
- 仿函数:
greater<int>()
- 迭代器:
vector<int>::iterator it = a.begin()
1. 前言
STL作为一个封装良好、性能合格的C++标准库,灵活正确使用STL可以节省非常多的时间。
2. 常用容器
2.1 vector数组
连续的顺序的存储结构(和数组一样的类别),但是有长度可变的特性
2.1.1 常用方法
构造
vector<int> arr(长度,初值)
vector<int> arr; // 构造int数组
vector<int> arr(100); // 构造长度为100的int数组
vector<int> arr(100,1); // 构造长度为100,所有数据元素初值为1的数组
vector<vetor<int>> mat(100,vector<int>()); // 构造100行,不指定列数的二维数组
vector<vetor<int>> mat(100,vector<int>(666,-1)); // 构造100行,666列的二维数组,初始值为-1
尾插&尾删
push_back(elem)
pop_back()
vector<int>arr;
// 尾插元素
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
// 尾删元素
arr.pop_back();
for (auto i : arr)
{
cout << i << endl;
}
数组长度&数组空判断&改变长度
size()
empty()
resize(新长度,初值)
vector<int>arr;
arr.push_back(1);
arr.push_back(2);
arr.push_back(3);
cout << arr.size() << endl;
if(arr.empty())
{
cout<<"arr 为空"<<endl;
}
else
{
cout<<"arr 非空"<<endl;
}
arr.resize(10,2); // 改变长度,变长,初值为2,如果不设定初值默认为0
arr.resize(1); // 改短,删除尾部多余元素
2.1.2 适用情形
一般情况下vector
可以替换掉普通数组,另外vector
存放在堆空间中。
2.1.3 注意事项
提前指定长度
如果长度已经确定,那么应当直接在构造函数指定长度,而不是一个一个push_back,这样会减少因为动态分配内存消耗的时常
当心size_t溢出
vector获取长度的方法size()
返回值的类型是size_t
,通常的OJ平台是32位的编译器,size_t
类型的范围是[0,2^32)
vector<int>(65536);
long long a = a.size()*a.size(); // size_t会溢出
2.2 栈stack
#include <stack>
通过二次封装双端队列(deque)容器,实现先进后出(FILO)的数据结构
2.2.1 常用方法
stack<double> stk;
stk.push(1.0); // 入栈
stk.push(1.4);
stk.push(1.3);
stk.push(1.2);
cout << stk.size() << endl; // 查看大小
cout << stk.top() << endl; // 取栈顶元素
stk.pop(); // 出栈
cout << stk.top() << endl;
cout << stk.empty() << endl; // 判空
2.2.2 注意事项
stack是没法访问内部元素的,所以不要对它进行遍历操作
2.3 队列 queue
#include <queue>
通过二次封装双端队列(deque)容器,实现先进先出(FIFO)的队列数据结构
2.3.1 常用方法
queue<int> que;
que.push(1);
que.push(2);
que.push(3);
cout << que.front() << endl; // 队头
cout << que.back() << endl; // 队尾
que.pop(); // 出队
cout << que.front() << endl; // 队头
cout << que.back() << endl; // 队尾
que.pop(); // 出队
cout << que.front() << endl; // 队头
cout << que.back() << endl; // 队尾
cout << que.size() << endl; // 队列大小
cout << que.empty() << endl; // 判空
2.3.2 注意事项
和栈类似,不可访问内部元素
2.4 优先队列 priority_queue
#include <queue>
提供常数时间的最大元素查找,对数时间的插入和提取,底层原理是二叉堆
2.4.1 常用方法
构造
priority_queue<类型,容器,比较器> pque;
- 类型:要存储的数据类型
- 存储数据的底层容器,默认为
vector<类型>
- 比较器:比较大小使用的比较器,默认为
less<类型>
,可自定义
priority_queue<int> pque1; // 存储int的大顶堆
priority_queue<int,vector<int>,greater<int>> pque2; // 存储int的小顶堆
#include <iostream>
#include <queue>
using namespace std;
class Girl
{
public:
Girl(int age)
{
m_age = age;
}
int m_age;
};
// 仿函数
class my_greater
{
public:
bool operator()(const Girl& g1, const Girl& g2)
{
//return g1.m_age > g2.m_age; // 优先队列为小顶堆
return g1.m_age < g2.m_age; // 为大顶堆
}
};
int main(void)
{
priority_queue<int> pque; // 大顶堆,top取的是最大值
priority_queue<int,vector<int>,greater<int>> pque2; // 小顶堆,top取的是最小值
pque.push(1); // 入堆
cout << pque.top() << endl; // 堆顶
pque2.push(1); // 入堆
cout << pque2.top() << endl; // 堆顶
pque.push(3);
cout << pque.top() << endl;
pque2.push(3);
cout << pque2.top() << endl;
pque.push(2);
cout << pque.top() << endl;
pque2.push(2);
cout << pque2.top() << endl;
pque.push(4);
cout << pque.top() << endl;
pque2.push(4);
cout << pque2.top() << endl;
pque.pop(); // 堆顶出堆
cout << pque.top() << endl;
pque.pop(); // 堆顶出堆
cout << pque.top() << endl;
pque.pop(); // 堆顶出堆
cout << pque.top() << endl;
priority_queue<Girl, vector<Girl>, my_greater> pq3; // 自定义数据类型
Girl g1(1);
Girl g2(5);
Girl g3(8);
Girl g4(3);
Girl g5(2);
pq3.push(g1);
cout << pq3.top().m_age << endl;
pq3.push(g2);
cout << pq3.top().m_age << endl;
pq3.push(g3);
cout << pq3.top().m_age << endl;
pq3.push(g4);
cout << pq3.top().m_age << endl;
pq3.push(g5);
cout << pq3.top().m_age << endl;
return 0;
}
2.4.2 适用情形
持续维护元素的有序性:每次向队列插入大小不定的元素,或者每次从队列取最大/最小的元素
2.4.3 注意事项
- 仅堆顶可读
- 只可访问堆顶,其他元素都无法读取到
- 堆中的所有元素是不可修改的
2.5 集合set
#include <set>
提供对数时间的插入、删除、查找的数据结构。底层原理是红黑树
2.5.1 常用方法
构造
set<类型,比较器>st
- 类型:要存储的数据类型
- 比较器:比较大小使用的比较器,默认为
less<类型>
,可自定义
遍历
可使用迭代器进行遍历
其他
set<int> st;
st.insert(1);// 插入
st.insert(2);
st.insert(2);
st.insert(0);
st.insert(3);
st.erase(2); // 删除
if (st.find(1) != st.end()) // find查找元素
{
cout << "找到元素" << endl;
}
cout << st.count(2) << endl; // 查找元素的个数
for (auto &elem:st)
{
cout << elem << endl;
}
cout << st.size() << endl; // set大小
st.clear(); // 清空
cout << st.empty() << endl; // 判断是否为空
2.5.2 适用情形
- 元素去重: [ 1 , 1 , 3 , 2 , 4 , 4 ] → [ 1 , 2 , 3 , 4 ] [1,1,3,2,4,4]\rightarrow[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]\rightarrow[1,3,5,7,9] [1,5,3,7,9]→[1,3,5,7,9]
- 元素是否出现过
2.5.3 注意事项
不存在下标
set虽然可以遍历,但是只能使用迭代器进行遍历,不存在下标这一概念
元素只读
set的迭代器取到的元素是只读的,不可修改它的值
不可用迭代器计算下标
set的迭代器不能向vector一样相减得到下标
2.6 映射map
#include<map>
提供对数时间的有序键值对结构。底层原理是红黑树
2.6.1 常用方法
构造
map<键类型,值类型,比较器> mp
- 键类型:要存储键的数据类型
- 值类型:要存储值的数据类型
- 比较器:键比较大小使用的比较器,默认为
less<类型>
,可自定义
map<int,int> mp1; // int -> int 映射 按键从小到大
map<int,int,greater<int>> mp2; // int -> int映射 按键从大到小
遍历
可使用迭代器进行遍历
// 遍历
for (map<int, int>::iterator it = mp.begin(); it != mp.end(); it++) // 利用迭代器
{
cout << it->first << " " << it->second << endl; // first 是键,second是值
}
for (auto& i : mp)
{
cout << i.first << " " << i.second << endl;
}
其他
2.6.2 适用场景
需要维护映射的场景可以使用:输入若干字符串,统计每种字符串出现的次数(map<string,int>mp;
)
2.6.3 注意事项
中括号访问时默认值
如果使用中括号访问map时对应的键不存在,那么会新增这个键,并且值为默认值,因此中括号会影响键的存在。
不可用迭代器计算下 标
map的迭代器不能像vector一样,相减得到下标
2.7 字符串string
#include <string>
存储字符串
2.7.1 注意事项
尾接字符串一定要用+=
string的+=
运算符,将会在原字符串末尾尾接字符串。而+了再=赋值,会创建临时对象,再赋值给string,效率低。
substr()的参数为,第一个参数起始子串下标,第二个参数为子串长度,不是终点下标
find()方法的复杂度是o(n2),不是内置的KMP算法
2.8 二元组pair
#include<utility>
存储二元组(两个元素的一个组合)的数据结构
2.8.1 常用方法
构造
pair<第一个值类型,第二个值类型> pr
- 第一个值类型:要存储的第一个值的数据类型
- 第二个值类型:要存储的第二个值的数据类型
赋值
老式
pair<int,char> pr = make_pair(1,'a');
c++11,列表构造
pair<int,char> pr = {1,'a'};
3. 迭代器iterator
3.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.beign()
是一个迭代器,指向的是第一个元素a.end()
是一个迭代器,指向的是最后一个元素再后面一位- 上述迭代器具有自增运算符,自增则迭代器向下一个元素移动
- 迭代器和指针类似,对它解引用
*it
,就能取到容器中的值了
3.2 为什么需要迭代器
很多数据结构并不是线性的(例如红黑树),对于非线性数据结构,下标是无意义的。无法使用下标来遍历整个数据结构。
迭代器的作用就是定义某个数据结构的遍历方式,通过迭代器的增减,代表遍历到的位置,通过迭代器便能够成功遍历非线性结构了。
3.3 迭代器用法
对于vector容器,它的功能比较完整,使用它来举例:
.beign()
,头迭代器.end()
,尾迭代器.rbegin()
,反向头迭代器.rend()
,反向尾迭代器- 迭代器
+
整型:将迭代器向后移动 - 迭代器
-
整型:将迭代器向前移动 - 迭代器
++
整型:将迭代器向后移动1位 - 迭代器
--
整型:将迭代器向前移动1位 - 迭代器
-
迭代器:两个迭代器的距离 prev(it)
:返回it的前一个迭代器next(it)
:返回it的后一个迭代器
对于其他的容器,由于其结构特性,上面的功能不一定都有(例如set的迭代器是不能相减求距离的)
3.4 常见问题
.end()
和.rend()
指向的位置是无意义的值
对于一个长度为10的容器:for(auto it = a.begin();it!=a.end();it++)
的.end()是无法访问的
不同容器的迭代器功能可能不一样
迭代器细化有:正向、反向、双向,每个容器的迭代器支持的运算符也可能不同,因此不同容器的迭代器细节很有可能是不一样的。
删除操作需要警惕
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}
// 因为在使用迭代器删除2之后,3,4向前移动,迭代器下一次指向就到了4
vector<int> a{1,2,3,4};
for (auto it = a.begin();it!=a.end();it++)
if(*it==4)
a.erase(it);
// RE,报错:
// 因为在使用迭代器删除4之后,it指向了a.end(),此时再++越界了
建议:如无必要,别用迭代器操作容器(遍历与访问没关系)
4. 常用算法
4.1 swap
交换元素
template<typename T>
void swap(T& a,T& b);
用法示例
string a = "a", b = "b";
cout << "a:" << a << ",b:" << b << endl;
swap<string>(a, b);
cout << "a:" << a << ",b:" << b << endl;
注意事项
swap的参数是引用
4.2 sort
使用快速排序给一个可迭代对象排序
用法示例
vector<int> arr{ 1,9,5,8,96,37,45,6,2 };
sort(arr.begin(), arr.end(), greater<int>()); // 从小到大排序
for (auto i : arr)
{
cout << i << endl;
}
默认排序方式是从大到小,如果需要从小到大,传入比较器
如果需要完成特殊情况的排序,则需要传入自定义比较器进去。
比较器的返回值是bool,传参是需要比较的两个元素。记我们定义的比较方法为 ⊗ \otimes ⊗:
- 如果a ⊗ \otimes ⊗b,即满足比较,则比较器函数应当返回:
true
- 如果a ⊗̸ \not\otimes ⊗b,即不满足比较,则比较器函数应当返回:
false
- 如果a = = == ==b,比较器函数应当返回:
false
4.3 lower_bound/upper_bound
在已升序排序的元素中,应用二分查找检索指定元素,返回对应元素迭代器位置,找不到则返回尾迭代器。
lower_bound()
:寻找 ≥ x \ge{x} ≥x的第一个元素的位置upper_bound()
:寻找 > x >x >x的第一个元素的位置
怎么找 ≤ x \le{x} ≤x或者 < x <x <x的第一个元素呢?- > x >x >x的第一个元素的前一个元素,便是 ≤ x \le{x} ≤x的第一个元素
- ≥ x \ge{x} ≥x的第一个元素的前一个元素,便是 < x <x <x的第一个元素
如何转换成下标?减去头迭代器即可。
用法示例
vector<int> arr{ 0,1,1,1,8,9,9 };
int pos_1 = lower_bound(arr.begin(), arr.end(), 8) - arr.begin(); // 找>=8的第一个元素的位置
int pos_2 = upper_bound(arr.begin(), arr.end(), 8)-arr.begin(); // 找>8的第一个元素的位置
cout << pos_1 << endl;
cout << pos_2 << endl;
4.4 reverse
反转一个可迭代对象的元素顺序
template<class BidirIt>
void reverse(BidirIt First,BidirIt Last);
用法示例
vector<int> arr{ 0,1,1,1,8,9,9 };
reverse(arr.begin() + 2, arr.end());// 从第二位开始反转,结果应该是:0,1,9,9,8,1,1
for (auto i : arr)
{
cout << i << endl;
}
4.5 max/min
返回最大值 / 最小值的数值
用法示例
// c++
int mx = max(max(1,2),max(3,4)); // 4
int mn = min(min(1,2),min(3,4)); // 1
// c++11之后
int mx = max({1,2,3,4});
int mn = min({1,2,3,4});
4.6 unique
消除数组的重复相邻元素,数组长度不变,但是有效数据缩短,返回的是有效数据位置的尾迭代器
例如: [ 1 , 1 , 4 , 5 , 1 , 4 ] → [ 1 , 4 , 5 , 1 , 4 , ? ] [1,1,4,5,1,4]\rightarrow[1,4,5,1,4,?] [1,1,4,5,1,4]→[1,4,5,1,4,?], ? ? ?位置为返回的迭代器指向的位置
template<class ForwardIt>
ForwardIt unique(ForwardIt First,ForwardIt Last);
单独使用unique是无法完全消除重复元素的,只能消除相邻重复元素。如果序列有序,那么它就能去重了。
但是它去重后,序列尾部会产生无效数据,为了去掉这些无效数据,我们需要结合erase
例如: [ 1 , 2 , 3 , 4 , 4 , 4 ] → [ 1 , 2 , 3 , 4 , ? ‾ , ? ] [1,2,3,4,4,4]\rightarrow[1,2,3,4,\underline?,?] [1,2,3,4,4,4]→[1,2,3,4,?,?]
vector<int> arr{ 1,2,1,4,5,4,4 };
// 先排序
sort(arr.begin(), arr.end()); // 降序排列
arr.erase(unique(arr.begin(), arr.end())); // 去重 // 删除无效数据
for (auto i : arr)
{
cout << i << endl;
}
return 0;
4.7 数学函数
所有函数参数均支持int/long long/float/double/long double
4.8 gcd/lcm
返回最大公因数和最小公倍数
c++17
int x = gcd(8,12); // 4
int y = lcm(8,12); // 24
直接实现gcd/lcm
int gcd(int a,int b)
{
if(!b)
return a;
return gcd(b,a%b);
}
int lcm(int a,int b)
{
return a/gcd(a,b)*b;
}