文章目录
本篇较为详细的讲解算法竞赛中常用到的STL内的几种常用容器及函数,其中包括各种容器的特点、函数、在算法竞赛中的应用,以及我对各种容器的理解。文中对容器理解有误的地方,请多多指正。
本篇持续更新中…
vector(数组)
vector可理解为变长数组,也就是不定长数组,在声明一个vector型的元素作为数组时,不需要声明它的长度。
vector支持随机访问,对于任意下标 0 <= i <= n,可以像数组一样用[ i ]取值,但它不支持在任意位置O(1)插入。
迭代器
迭代器就相当于STL中的指针,迭代器的声明方式为:
vector<int>::iterator it; //保存int型的vector容器的迭代器
vector的迭代器的操作方式与指针相似,两个迭代器可以进行相加减。迭代器与整数相加就相当于指针的移动;两个迭代器相减也和指针相减含义一样,其结果就是两个迭代器对应下标的距离。
vector中的begin和end函数分别返回的是vector容器中的头部和尾部,相当于v[0]和v[v.size()]。
按照数组方式和迭代器方式遍历整个数组:
for(int i = 0; i < v.size(); i ++ ) cout << v[i] << " ";
cout << endl;
for(vector<int>::iterator it = v.begin(); it != v.end(); it ++ ) cout << *it << " ";
cout << endl;
vector中的常用函数
STL的所有函数都支持size和empty函数,下文介绍中将不再赘述
//声明
#include<iostream>
#include<vector> //vector的头文件
vector<int> v; //声明一个int型的vector容器
vector<int> a[114] //声明一个int型的二维vector容器,相当于二维数组
struct node {int x, y, z;}; //
vector<node> b; //声明一个node型的vector容器
int main(void) {
for(int i = 0; i < 10; i ++ ) {
v.push_back(i);
}
std::cout << v.size() << endl; //返回v的实际长度(包含的元素个数)
/*返回:10 */
std::cout << v.empty() << endl; //判断是否为空,当v为空时返回true/1,否则返回false/0
/*返回:0 */
//v.clear(); //清空容器
std::cout << v.front() << endl; //返回当前v的首个元素
/*返回:0 */
std::cout << v.back() << endl; //返回当前v的最后一个元素
/*返回:9 */
v.pop_back(); //删除v的最后一个元素
return 0;
}
vector数组实现sort排序
sort(s.begin(), s.end(), [&](auto a, auto b){return a > b});
【实例】用vector代替邻接表保存有向图
coust int MAX_EDGES = 100010;
vector<int> ver[MAX_EDGES], edge[MAX_EDGES];
//保存从x到y权值为z的有向边
void add(int x, int y, int z) {
ver[x].push_back(y);
edge[x].push_back(z);
}
//遍历从x出发的所有边
for(int i = 0; i < ver[x].size(); i ++ ) {
int y = ver[x][i], z = edge[x][i];
}
queue(队列)
队列顾名思义,它的存储方式就像队列一样,是按照插入队列的顺序进行排序,是一种先进先出的思想。
queue队列经常在图的广度优先搜索bfs和最短路算法spfa中应用。
queue中的常用函数
#include<queue> //queue容器的头文件
queue<int> q; //
struct node {int x, y, z;};
queue<node> qq; //声明一个结构体类型的队列容器
q.push(111); //将111插入队尾 O(1)
q.pop(); //将队头元素删除 O(1)
int x = q.front(); //返回队头元素 O(1)
int y = q.back(); //返回队尾元素 O(1)
priority_queue(优先队列)
priority_queue 可以理解为一个大根二叉堆。
关于二叉堆的介绍:https://blog.csdn.net/weixin_73523694/article/details/131626953
#include<queue>
priority_queue<int> q; //声明
priority_queue<pair<int, int>> qq; //声明一个二元组类型的队列
q.push(111); //将元素插入队列中 O(log n)
q.pop(); //删除队头元素, O(log n)
int x = q.top(); //返回队列中最大元素,O(1)
通常二叉堆结构支持将元素插入堆、删除堆顶元素、查询堆顶元素、删除堆中任意元素四种操作,但是用priority_queue不支持删除堆中任意元素。
虽然不支持删除任意元素,但是我们可以模拟删除的操作。我们只需将待删除元素标记,当从堆顶取出元素被标记时,我们可以忽略此次操作,重复进行本次操作,即可实现删除操作。这种操作也被称作懒惰删除法。
priority_queue默认大根堆,即队头元素为最大值,但当我们我们储存的是自定义的结构体类型时,需要我们重载 " < " 运算符。
例如下面的node结构体储存了二维平面的编号id和坐标(x, y),比较大小时,先比较横坐标,再比较纵坐标。
struct node{int id; double x, y;};
const double eps = 1e-8;
bool operator < (const node &a, const node &b) {
return a.x + eps < b.x || a.x < b.x + eps && a.y < b.y;
}
priority_queue默认大根堆,那我们想要使用小根堆时该如何处理呢?
- 当堆中储存的元素类型为int或double型时,我们可以在将元素存入堆中时,将元素取相反数,然后取出元素时,再将元素取相反数,即实现小根堆。
- 我们还可以直接定义完整的优先队列,实现小根堆。
- 通用的做法:重载 " < "运算符。
priority_queue <int,vector<int>,greater<int> > q; //小根堆
priority_queue <int,vector<int>,less<int> >q; //大根堆
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。\
其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
struct node{int id; double v;};
bool operator < (const node &a, const node &b){
return a.v > b.v;
}
deque(双端队列)
双端队列deque是一个支持在两端高效插入或删除元素的连续线性储存空间。它像是vector和queue的结合。与vector相比,deque在头部增删元素仅需要O(1)的时间;与queue相比,deque像数组一样支持随机访问。
deque中的常用函数
#include<deque> //deque的头文件
deque<int> q; //声明一个int型的deque容器
int x = q[i]; //随机访问
int y = q.front(), z = q.back(); //返回队头/尾元素
q.push_back(x); //从队尾入队
q.push_front(x); //从队头入队
q.pop_front(); //删除队头元素
q.pop_back(); //删除队尾元素
q.clear(); //清空容器
set(有序集合)
set有序集合,顾名思义,set容器内部支持自动去重排序,set不支持插入重复的元素。
multiset(有序多重集合)
multiset有序多重集合,支持排序,不支持去重,容器内部可包含多个相等元素。
set和multiset的内部实现是一棵红黑树(平衡树的一种),它们支持的函数基本相同。
s.insert(key); //向set中插入一个元素key。
s.erase(key); //从set中移除指定的元素key。
s.clear(); //清空set中的所有元素。
s,begin(); //指向集合中最小元素的迭代器
s.end(); //指向集合中最大元素的下一个位置的迭代器
s.find(key); //查找set中是否存在指定的元素key,如果存在则返回指向该元素的迭代器,否则返回set::end()。
s.count(key); //返回set中与指定元素key相等的元素的个数,由于set中不允许有重复的元素,因此返回值只能是0或1。
s.lower_bound(key); //返回指向第一个不小于给定键值key的元素的迭代器。
s.upper_bound(key); //返回指向第一个大于给定键值key的元素的迭代器。
除了上述常用的内置函数外,还可以使用迭代器遍历set中的所有元素。需要注意的是,set中的元素是按照键值进行排序的,默认情况下是按照升序排列,也可以通过自定义比较函数来指定排序方式。
unordered_set(不排序集合)
unordered_set是一个集合容器,它存储唯一的元素,并且元素是无序的。它的元素可以是任何类型,但是必须是唯一的。与set相比,unordered_set的插入、删除和查找操作都更快,但是它的元素是无序的。
undered_set<int> s;
s.insert(key); //向unordered_set中插入一个元素key。
s.erase(key); //从unordered_set中移除指定的元素key。
s.clear() //清空unordered_set中的所有元素。
s.find(key); //查找unordered_set中是否存在指定的元素key,如果存在则返回指向该元素的迭代器,否则返回unordered_set::end()。
s.count(key); //返回unordered_set中与指定元素key相等的元素的个数,由于unordered_set中不允许有重复的元素,因此返回值只能是0或1。
除了上述常用的内置函数外,还可以使用迭代器遍历unordered_set中的所有元素。
stack(栈)
stack是一种先进后出(LIFO)的数据结构,只能从栈顶进行插入和删除操作,不支持在其他位置访问或修改元素。
stack<int> stk;
stk.push(x);//将x加入栈中,即入栈操作
stk.pop();//出栈操作(删除栈顶),只是出栈,没有返回值
stk.top();//返回第一个元素(栈顶元素)
stk.size();//返回栈中的元素个数
stk.empty();//当栈为空时,返回 true
map(映射)
map容器是一个键值对 key - value 的映射。其内部实现是一棵以key为关键码的红黑树。map的key和value可以是任意类型,其中key必须定义“小于号”运算符
声明方式:map<key_type, value_type name;
例如:
map<long long, bool> vis;
map<string, int> hash;
map<pair<int, int>, vector<int> > test;
在很多时候,map容器被当作Hash表使用,建立从复杂信息key(如字符串)到简单信息value(如一定范围内的整数)的映射。
因为map基于平衡树实现,所以它的大部分操作的时间复杂度都在O(log n)级别,比Hash函数实现的传统Hash表慢。
unordered_map
unordered_map是C++标准库中的一个关联容器,它提供了一种键值对(key-value)的映射,其中的键是唯一的,而值则可以重复。与map容器相比,unordered_map使用哈希表(hash table)实现,因此它具有更快的插入、查找和删除操作的能力,但是迭代顺序是不确定的。
迭代器操作:unordered_map和map都支持迭代器的使用,可以通过迭代器遍历容器中的元素,包括正向遍历和反向遍历。
map / undered_map 内置函数
map<string, int> h;
undered_map<string, int> operator;
h.insert() //插入键值对到map中。有多种形式的insert()函数重载,可以接受单个键值对、迭代器范围或者初始化列表作为参数。
h.erase() //从map中删除指定键的键值对。有多种形式的erase()函数重载,可以接受单个键、迭代器范围或者删除满足特定条件的键值对。
h.find() //查找指定键的位置,并返回指向该位置的迭代器。如果键不存在,则返回map的end()迭代器。
h.count() //返回指定键在map中的个数。由于map中的键是唯一的,因此返回值只能是0或1。
h.size() //返回map中键值对的个数。
h.empty() //检查map是否为空。如果map为空,则返回true,否则返回false。
h.clear() //清空map,删除所有的键值对。
operator[] //访问指定键的值。如果键存在,则返回对应的值;如果键不存在,则会插入一个新的键值对,关联的值被默认构造。
如果多次查找的key不存在,当时间一长,容器里会多很多无用的0值,占用内存。
在使用 [ ] 操作符之前,最好先用find函数检查key的存在性
使用unordered_map容器的示例代码
#include <iostream>
#include <unordered_map>
#include <string>
int main() {
// 创建一个unordered_map容器
std::unordered_map<int, std::string> myMap;
// 插入键值对
myMap.insert({1, "one"});
myMap.insert({2, "two"});
myMap[3] = "three";
// 访问值
std::cout << myMap[1] << std::endl; // 输出: one
// 检查键是否存在
if (myMap.count(2) > 0) {
std::cout << "Key 2 exists." << std::endl;
}
// 遍历键值对
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 删除键值对
myMap.erase(3);
return 0;
}
string(字符串)
string字符串,是c++中的一个类,也可看作是一个字符串容器,可以说是非常好用。它可以直接将两个字符串进行比较。另外其内置函数功能也是十分齐全。
#include<cstring>
string str;
str.push_back('s');//在末尾添加字符 ‘s’
str.pop_back();//删除最后一个元素
str.front();//返回第一元素
str.back();//返回最末元素
str.clear();//清空容器
str.empty();//容器为空返回true, 否则返回 false
str.erase(3);//删除 str[3]~str[n-1], 返回str
str.erase(2, 3);//删除 str[2]~str[2+3-1]
str.erase(str.begin());//删除指向的元素, 返回迭代器, 指向下一元素
str.erase(str.begin(), str.end());//删除区间内的元素, 返回迭代器, 指向下一元素
str.find('s');//返回字符 ‘s’ 在 str 中首次出现的位置
str.find('s', 2);//返回字符 ‘s’ 在 str[2]~str[n-1] 中首次出现的位置
str.find(ch);//返回字符串 ch 在 str 中首次出现的位置
str.find(ch, 4);//返回 ch 在 str[4]~str[n-1] 中首次出现的位置
str.find(ch, 4, 3);//返回 ch[0]~ch[3-1] 在 str[4]~str[n-1] 中首次出现的位置
str.find(str1);//返回 str1 在 str 中首次出现的位置
str.find(str1, 2);//返回 str1 在 str[2]~str[n-1] 中首次出现的位置
str.substr(2);//返回 str[2]~str[n-1], 不对 str 进行操作
str.substr(2,3);//返回 str[2]~str[2+3-1]
str.replace('a', 'b');//讲st字符串中第一个‘a’替换为‘b’
str.replace(0, 3, "abc");//将str字符串中0-3位置的字符串替换为"abc"
str.replace(3, 4, 5, 'x');//用5个'x'字符替换str中从3开始的长度为4的字符串
stringstream
std::stringstream是C++标准库中的一个类,它提供了字符串流的功能,可以将数据以字符串的形式进行输入和输出。std::stringstream可以将各种类型的数据转换为字符串,并且可以从字符串中提取数据。
#include<iostream>
#include<algorithm>
#include<sstream>
using namespace std;
int main(){
//1、创建一个对象,向对象输入字符串,输入字符串后直接进行字符串拼接
stringstream ss;
ss << str;
ss1 << "fre";
ss3 << "gre";
cout << ss1.str() << endl;
//2、创建时使用字符串初始化,进行字符串拼接时,首先把原本的字符串覆盖掉,之后再进行拼接。
streamstring ss(str);
//3、输出时需调用str()函数
cout << ss.str() << endl;
//4、利用stringstream去除字符串空格,streamstring 默认是以空格来分割字符串
stringstream ss("2 dfjho 43");
cout << ss.str() << endl;
string str;
while (ss >> str){
cout << str << endl;
}
//5、利用 streamstring 指定字符分割字符串
string source = "abc,123,<!>";
stringstream ss(source);
cout << ss.str() << endl;
cout<< endl;
string str;
while (getline(ss, str, ',')){
cout << str << endl;
}
}
std::stringstream还提供了其他一些功能,如clear()函数用于重置字符串流的状态,str()函数用于获取字符串流的内容,以及seekg()和seekp()函数用于设置读写位置。
std::stringstream对于将数据转换为字符串或从字符串中提取数据非常有用,尤其在需要处理字符串形式的数据时。
bitset
bitset可看作一个多位二进制数,每8位占用一个字节,相当于采用了状态压缩的二进制数组,并支持基本的位运算。在估算程序运行的时间时,我们一般以32位整数的运算次数位基准,因此n位bitset执行一次位运算的复杂度可视为 n / 32 n/32 n/32,效率较高。
bitset声明时可初始化,可用unsigned或string型来赋值。当用unsigned long值作为bitset对象的初始值时,该值将转化为二进制的位模式。而bitset对象中的位集作为这种位模式的副本。如果bitset类型长度大于unsigned long值的二进制位数,则其余的高阶位置为0;如果bitet类型长度小于unsigned long值的二进制位数,则只使用unsigned值中的低阶位,超过bitet类型长度的高阶位将被丢弃。
操作符
~s : 返回对bitset s按位取反的结果
&,|,^ : 返回丢两个位数相同的bitset执行按位与、或、异或运算的结果
“>>”,“<<”: 返回把一个bitset右移、左移若干位的结果
==,!= : 比较两个bitset代表的二进制数是否相等
bitset支持[ ]操作符,bitset[i]表示二进制的第i位,可以赋值或取值。
//头文件
#include<bitset>
//声明/初始化
bitset<n> s; //表示一个n位的二进制数,每位都是0
bitset<n> s(m) //表示一个n位的二进制数,为m的二进制表示
string s;
bitset<n> s(str) //将string行转化为bitset型,前提是str为01字符串
bitset<n> s(s, a, b) //将字符串s的下标a开始,长度为b的子字符串与bitset进行转换
bitset和string是反向转化的,但仅限与string型小于bitset位数的部分,超出bitset位数的部分将被抛弃,在bitset位数内,string反向转换位bitset型。
bitset与整型数转换时,bitset从第0位开始存储整型数二进制下的最低位,当整型数的二进制位数超过bitset位数时,超出部分将不会被读进。
遇到整型数或者string型转bitset型时,当整型数二进制或string长度小于bitset时,bitset的高阶补0。
举个例子:
整型数15,二进制为0100,0001,正常转换为8位bitset型为1000,0010, string型转换同理
但是如果0100,0001转换为4位bitset型则为: 1000,因为bitset是从15的二进制的第0位开始储存
string型的"01000001"转换位4位bitset型则为: 0010,因为bitset是将string的前四位反向读取,而string的后四位抛弃了
常用内置函数
b.any() //b中是否存在为1的二进制位
b.none() //b中不存在置为1的二进制位
b.count() //b中置为1的二进制位的个数
b.size() //b中二进制位的个数
b.test(x) //b中在x处的二进制位是否为1?
b.set() //把b中所有二进制位都置为1
b.set(x) //把b中在x处的二进制位置为1
b.reset() //把b中所有二进制位都置为0
b.reset(x) //把b中在x处的二进制位置为0
b.flip() //把b中所有二进制位逐位取反
b.flip(x) //把b中在x处的二进制位取反
b.to_uiong() //用b中同样的二进制位返回一个unsigned long值
cout << b << endl; //直接输出b
pair(二元组)
C++内置二元组,其中包含两个元素,分别是first、second。在比较大小中,以第一元first尾第一关键字,第二元second尾第二关键字。
pair<int> a; A is an int-type binary
a.first = 1; a.second = 2;
pair<int, pair<int, int>> b;
b.first = 1;
b.second.first = 2; b.second.second = 3;
```>>,<<: 返回
```cpp
//头文件
#include<bitset>
//声明
bitset<n> s; //表示一个n位的二进制数,每位都是0
bitset<n> s(m) //表示一个n位的二进制数,为m的二进制表示
string s;
bitset<n> s(str) //将string行转化为bitset型,前提是str为01字符串,string对象和bitset对象之间是反向转化的