C++STL容器的使用
§ STL
-
标准模板库,日常简单理解为标准库
-
这三个字母在C++标准文档中没有出现,是人为制造出来的东西。于是STL的概念就没那么清楚、比较模糊,比如string是标准库里的,但不是标准模板库里的。因此string是不是STL里的不好说
-
不用纠结定义,就当奇闻轶事乐趣
-
6大组件
①容器
②迭代器:理解成指针
③适配器:容器进行二次封装得到的
④算法:如sort、lower_bound
⑤仿函数:C++特性,重载了()使得具有函数功能
⑥分配器:理解为C语言的malloc、calloc
1. string
- 头文件
include <string>
,string s;
对应类 对象
,如同int a
对应类型 名
- 获取字符串长度
s.size();
s.length();
//返回值是 size_type,与int比较时得强转类型
//两个函数没有任何区别
错误想法:将字符串中的某一个字符改成字符串结束标志,希望把字符串给截掉
string s = "123";
s[1] = '\0';
cout << s.size();
- 查找
s.find("abc");
//s.find(查找什么, 下标从ind开始往后找而省略时默认为0)
//返回值也是size_type
//没找到返回值string::npos(no position 的缩写)
//没找到的返回值表现为unsigned long long的极大值即long long -1
//判断是否找到
if (s.find(...) == string::npos)
//将string::nops改成-1不太严谨
- 截取
s.substr(ind, 长度)
//ind指的是从下标为ind的位置开始截取
s.substr(ind);
//从ind开始截取到末尾
- 替换
s.replace(ind, 被替换字符串的长度, 替换成什么);
//删除,替换成空字符串即可
- 插入
s.insert(ind, 插入什么);
以上只是常见用法,在算法做题中够用,其它重载的功能可以在cppreference.com中查看
2. vector
-
向量,长度不定的动态数组。头文件
#include <vector>
,类型定义vector<类型> 变量名
-
判空
v.empty()
,一般不怎么用,一般用下面的 -
返回元素数量
v.size()
-
在vector的最后加入元素x
v.push_back(x)
-
清空
v.clear()
以上只是写算法题时常用的,在指定位置插入元素略过,因为要使用迭代器 -
初始化与遍历
通过下标访问
//初始化10个int元素,默认0
vector<int> v1(10);
//访问下标为3的元素
//访问跟数组访问的方式一样
cout << v1[3] << endl;
//初始化10个9
vector<int> v2(10, 9);
//二维数组
//下面的两个>分开是因为C++11标准前的编译器会把>>当成右移运算符
vector<vector<int> > v3(5, vector<int>(6, 10));
//初始化5行的一维数组,每个一维数组存放6个元素10
//列表初始化、遍历 C++11特性
vector<int> v = {1, 2, 3, 4};
//auto自动推导类型
for (auto x : v) {
cout << x << endl;
}
- vector有限空间+动态扩容
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
int t = 0;
for (int i = 0; i < 100000; i++) {
v.push_back(1);
if (t != v.capacity()) {
t = v.capacity();
cout << t << endl;
}
}
return 0;
}
扩容规则取决于环境、编译器
- 时间复杂度
来源:cppreference
3. stack
- 栈,
stack<int> sta;
- 判空
sta.empty()
- 元素数量
sta.size()
- 入栈
sta.push(x)
下面两个操作得保证栈非空
- 出栈
sta.pop()
- 访问栈顶元素
sta.top()
栈是用什么实现的?stack<int, 模板参>
,默认使用双端队列deque
(队首队尾都可以入队出队),实现栈只需要关闭一些双端队列的功能。那么双端队列经过二次封装得到了栈这个容器适配器。
4. queue
queue<int> que
,跟stack不能说一模一样,只能说是完全一致- 判空
- 元素数量
que.push(x)
,入队尾;que.pop()
,队首出- 访问队首元素
que.front()
;访问队尾que.back()
演示:玩一个花的。只要是类型已知,都可以存,同理也可以存储自定义类型
#include <iostream>
#include <queue>
using namespace std;
int main() {
int num[10] = {1, 2, 3, 4, 5};
queue<int *> que;
que.push(&num[2]);
que.push(&num[4]);
while (!que.empty()) {
int *p = que.front();
que.pop();
cout << *p << endl;
}
return 0;
}
自定义类型
#include <iostream>
#include <queue>
using namespace std;
struct node {
int x, y;
};
int main() {
queue<node> que;
que.push((node){4, 6});
//que.push({4, 6});
que.push({0, 9});
que.push({5, -1});
que.push({0, 0});
cout << "元素数量:" << que.size() << endl;
while (!que.empty()) {
node temp = que.front();
que.pop();
cout << temp.x << " " << temp.y << endl;
}
return 0;
}
- 实现方式和栈一样,是一个容器适配器
5. deque
-
双端队列,顺序容器,支持随机访问即可以通过
[]
直接访问
基本操作 -
判空、获得数量
-
插入:队尾
que.push_back(x)
,队首que.push_front(x)
-
删除:队尾
que.pop_back()
,队首que.pop_front()
-
访问队首队尾元素:
que.back()
,que.front()
-
问题:以上操作时间复杂度都是常数级别O(1),随机访问不是应该内存连续?双端队列底层实现逻辑:内存不连续,二级指针,通过偏移量访问
6. priority_queue
- 优先队列,和queue共用一个头文件
- 默认是大顶堆
- 操作和队列一样,除了访问元素。访问堆顶元素:
que.top()
- 用小顶堆,用到第二个第三个模板参:
priority_queue<类型, 用什么容器存储, 排序方法>
,其中“排序方法”默认是less<int>
,与之对应的就是greater<int>
表示小顶堆 - C++中还有一个
make_heap
函数利用数组建堆,得到优先队列 - 自定义类型
#include <iostream>
#include <queue>
using namespace std;
struct node {
int x, y;
//跟sort一样必须指定排序方法
//堆里面的重载逻辑反着来
bool operator<(const node &b) const {
//<号,大顶堆
return this->x < b.x;
}
};
//方法二仿函数:重载括号的类
struct cmp {
bool operator() (const node &a, const node &b) const {
return a.x < b.x;
}
};
int main() {
priority_queue<node> que;
//priority_queue<node, vector<int>, cmp> que;
que.push({5, 6});
que.push({10, 1});
que.push({7, 9});
que.push({-1, 2});
que.push({6, 8});
while (!que.empty()) {
node temp = que.top();
que.pop();
cout << temp.x << " " << temp.y << endl;
}
return 0;
}
7. set
- 集合,有序、去重,默认递增,自定义类型得指定排序规则
- 底层实现:现在能见到的的编译器都用红黑树实现,因为红黑树比较合适。但是标准没有定义,只规定插删等操作是平均对数级别限制复杂度
- 判空、返回集合元素数量
- 插入
s.insert(x)
- 删除
s.erase(x)
- 查找
s.count(x)
返回x的数量(要么是0要么是1),s.find(x)
返回的是指针/迭代器
代码演示1
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s;
s.insert(5);
s.insert(8);
s.insert(2);
s.insert(-1);
s.insert(0);
cout << s.size() << endl;
if (s.count(5) == 1) {
cout << "5 yes" << endl;
} else {
cout << "5 no" << endl;
}
s.erase(5);
if (s.count(5) == 0) {
cout << "5 no" << endl;
}
for (auto it = s.begin(); it != s.end(); it++) {
cout << (*it) << " ";
}
/*
for (auto x : s) {
cout << x << " ";
}
//反向迭代器
for (auto it = s.rbegin(); it != s.rend(); it++) {
cout << (*it) << " ";
}
*/
cout << endl;
return 0;
}
结果
代码演示2:自定义类型指定排序方法
#include <iostream>
#include <set>
using namespace std;
struct node {
int x, y;
bool operator<(const node &a) const {
return x + y > a.x + a.y;
}
};
int main() {
set<node> s;
s.insert({5, 0});
s.insert({7, 2});
s.insert({4, 9});
s.insert({11, -1});
cout << s.size() << endl;
for (auto it = s.begin(); it != s.end(); it++) {
cout << it->x << " " << it->y << "\tsum = " << it->x + it->y << endl;
//it当成指针使用
}
return 0;
}
结果
8. map
- 映射,唯一关系、键值对存储;有序(根据键排序)去重,map的升级版
map<int, string> m
,键值对pair<key, value>
将两个元素糅在了一起(key→first,value→second),而map中存储的正是这种键值对。键key是唯一的- 判空、元素数量
- 插入
m.insert(pair)
- 删除
m.erase(key)
删除键即可 - 查找
m.count(key)
或m.find(key)
- map重载的中括号
[]
,直接用m[key]
修改或插入,因此通常都使用中括号插入元素!
代码演示
#include <iostream>
#include <map>
#include <string>
#include <utility>
using namespace std;
int main() {
map<string, int> m;
cout << "map size = " << m.size() << endl;
m.insert(make_pair("abc", 99));
m["abcd"] = 10;
m["+-+-+"] = 12;
m["324"] = 22;
cout << "map size = " << m.size() << endl;
if (m.count("abcd") == 1) {
cout << "abcd yes, value = " << m["abcd"] << endl;
}
m.erase("324");
cout << "map size = " << m.size() << endl;
for (auto it = m.begin(); it != m.end(); it++) {
cout << it->first << ", " << it->second << endl;
}
/*
if (m["ddddd"]) {
cout << "yes" << endl;
}
*/
//功能上没问题,但是这里会创建一个键为"ddddd"的键值对,int的默认值是0
return 0;
}
结果
9. C++11后的容器
unordered_set和unordered_map
- 用hash实现的集合和映射,简单的用法与set、map完全一样
- 头文件分别为自己的名字
#include<set> | #include<map> | |
---|---|---|
有序去重 | set | map |
有序不去重 | multiset | multimap 中括号访问的是任意的,一般不用 |
#include<unordered_set> | #include<unordered_map> | |
---|---|---|
无序去重 | unordered_set | unordered_map |
#include<unordered_multiset> | #include<unordered_multimap> | |
---|---|---|
无序不去重 | unordered_multiset | unordered_multimap |
- 用自定义类型的unordered_map需要重载hash函数与==运算符
刷题
1.字符串括号匹配
378
样例输入1
a(cc[])bbb()@
样例输出1
YES
样例输入2
a(cc[)]bbb()@
样例输出2
NO
分析:
- 只需要把括号入栈,左括号全入栈,右括号匹配出栈
- 如果不匹配直接输出
- 匹配完若栈不为空说明还有多余的左括号,不匹配
#include <iostream>
#include <string>
#include <stack>
using namespace std;
int main() {
stack<char> sta;
string s;
cin >> s;
int f = 0; // 1不匹配 0匹配
for (auto c : s) {
if (c == '(' || c == '[' || c == '{') {
sta.push(c);
} else if (c == ')') {
if (!sta.empty() && sta.top() == '(') {
sta.pop();
} else {
f = 1;
break;
}
} else if (c == ']') {
if (!sta.empty() && sta.top() == '[') {
sta.pop();
} else {
f = 1;
break;
}
} else if (c == '}') {
if (!sta.empty() && sta.top() == '{') {
sta.pop();
} else {
f = 1;
break;
}
}
}
if (!sta.empty()) f = 1;
if (f == 1) cout << "NO" << endl;
else cout << "YES" << endl;
return 0;
}
2.仓库日志
379
样例输入
13
0 1
0 2
2
0 4
0 2
2
1
2
1
1
2
1
2
样例输出
2
4
4
1
0
分析:建立一个辅助栈,存储最大值,每当存储的货物数量小于等于最大值栈的top元素值,则将此最大值再入一遍;若大于最大值,则入当前值。当然最开始先把0压栈。出栈时一同出栈即可(货物栈可以不需要)。
3.编辑
482
样例输入
dmih
11
B
B
P x
L
B
B
B
P y
D
D
P z
样例输出
yxz
分析:光标左右边分别建立栈
4.敲七
384
样例输入
4 3 6
样例输出
3
分析
- 约瑟夫环问题,但这里用数学公式有点麻烦
- 用队列模拟,判断队首,若安全则拿到队尾;否则直接出队
- 当队列元素为1时直接输出
5.查字典
575
样例输入
2
scan
10
word
15
2
scan
word
样例输出
10
15
分析
- unordered_map快一些
6.溶液模拟器
569
样例输入
100 100
2
P 100 0
Z
样例输出
200 50.00000
100 100.00000
分析
- 溶液质量=溶剂+溶质(盐水=盐+水);浓度=溶质/溶液(盐/盐水)
- 有撤销操作肯定需要栈,存储每步加入的盐水的信息(自定义类型)
- 自定义结构
struct node {
double v, c, salt;//体积、浓度、盐的质量
//salt = v * c / 100; //c的符号是%
};
7.上网统计
566
样例输入
5 7
goodstudyer bookshopa
likespacea spaceweb
goodstudyer bookshopb
likespaceb spaceweb
likespacec spaceweb
likespacea juiceshop
gameplayer gameweb
样例输出
goodstudyer bookshopa bookshopb
likespacea spaceweb juiceshop
likespaceb spaceweb
likespacec spaceweb
gameplayer gameweb
分析
- 可以用map,用户 映射 下标
- vector二维数组,每个一维数组存储下标对应的网页地址
- 做法不唯一
其它题目573(用优先队列)