前言
在讲STL标准库之前,我们先来讲一下什么是数据容器。数据容器说白了就是。一个用来储存数据的结构。最典型也是最普遍的一个数据容器就是数组。数组很容易就能修改其中一个值,并且花费很少的时间,但我们也很容易就会发现数组的弊端,那么便出在了查找上。我们要是想查找这个数是否处于这个数组中,有两种方法,一种是用一个位图,需要另外开一个特别大的空间复杂度,并且开的空间还必须限制数据范围,10^9就不能开了。另外一种方法就是打一遍这个数组,一个个的查找,但这样的话是O(n)的时间复杂度。二分的查找方法确实能够使数组查找的时间复杂度降低为log(n)。但是二分需要先将数组进行排序,排序算法也本身是需要消耗时间的。也就是说,在处理某些问题的时候,我们的数组不够用了,这时候我们就需要其他的数据容器。好消息是,C++的STL标准库提供大多数的数据容器。这样也就减少了我们手写数据容器的时间,大大的加快了编程的效率。
顺序结构
向量(vector)
定义
包含着一系列连续存储的元素,约等于一个数组。访问向量中的任意元素或从末尾添加元素都可以在O(1)的时间复杂度内完成,而查找特定值的元素所处的位置或是在向量中插入元素则是O(n)的时间复杂度。
函数
vector <type > ve;构造一个向量
ve.assign(iterator start,iterator end);函数要么将区间[start, end)的元素赋到当前向量
ve.assign(type num,type val);赋num个值为val的元素到向量中
type a=ve.at(type n);//返回第n个元素的值(会判断是否越界,比较安全)
type a=ve.back();//返回最后一个元素的值
type a=ve.begin();//返回第一个元素的迭代器
ve.clear();//清空
ve.empty();//判断是否为空
type a=ve.end();//返回最后一个元素的迭代器
ve.erase(iterator n);//删除第n个元素
ve.erase(iterator start,iterator end);//删除区间[start, end)的元素
type a=ve.front();//返回第一个元素的值
ve.insert(iterator n,type val);//在指定位置n前插入值为val的元素,返回指向这个元素的迭代器
ve.insert(iterator n,type num,type val);//在指定位置n前插入num个值为val的元素
ve.insert(iterator n,iterator start,iterator end);//在指定位置loc前插入区间[start, end)的所有元素
ve.push_back(type a);//添加值为a的元素到当前向量末尾
ve.pop_back();//弹出当前向量末尾元素
type a=ve.size();//返回当前向量所容纳元素的数目
链表(list)
定义
链表将元素按顺序储存在链表中. 与 向量相比, 它允许快速的插入和删除,但是随机访问却比较慢.
函数
//list大多函数同向量
li.unique();//函数删除链表中所有重复的元素
li.push_front(type a);//函数将a连接到链表的头部。
li.pop_front();//函数删除链表的头部。
双端队列(deque)
定义
双向队列和向量很相似,但是它允许在容器头部快速插入和删除(就像在尾部一样)。
函数
同上:向量+链表
容器适配器
栈(stack)
定义
是一个容器类的改编,为程序员提供了堆栈的全部功能,也就是说实现了一个先进后出(FILO)的数据结构。
你可以想象一下你堆在一起的的盘子
函数
stack<type>st;//定义一个栈
st.empty();//判断是否为空
st.pop();//弹出栈顶元素
st.push(type a);//向栈中压入元素a
type a=st.top();//返回栈顶元素
type a=st.size();//返回栈的元素个数
队列(queue)
定义
C++队列是一种容器适配器,它给予程序员一种先进先出(FIFO)的数据结构。
想象一下排队的场景……
函数
queue<int>qu;//定义一个队列
qu.empty();//判断队列是否为空
qu.pop();//删除第一个元素
qu.size();//返回元素个数
qu.front();//返回第一个元素
qu.push();//在队尾添加元素
优先队列/堆(priority_queue)
定义
C++优先队列类似队列,但是在这个数据结构中的元素按照一定的断言排列有序。
函数
priority_queue<int>qu;//定义一个堆
qu.empty();//判断堆是否为空
qu.pop();//删除第一个元素
qu.size();//返回元素个数
qu.top();//返回第一个元素
qu.push();//加入一个元素
关联容器
Bitsets
定义
C++ Bitsets给程序员提供一种位集合的数据结构。Bitsets使用许多二元操作符,比如逻辑和,或等。
函数
Constructors;// 创建新bitsets
Operators;// 比较和赋值bitsets
bi.any();// 如果有任何一个位被设置就返回true
bi.count();// 返回被设置的位的个数
bi.flip();//反转bits中的位
bi.none();//如果没有位被设置则返回true
bi.reset();// 清空所有位
bi.set();// 设置位
bi.size();// 返回可以容纳的位的个数
bi.test();// 返回指定位的状态
bi.to_string();// 返回bitset的字符串表示
bi.to_ulong();// 返回bitset的整数表示
平衡树(map/multimap)
定义
C++ Maps是一种关联式容器,包含“关键字/值”对
C++ Multimaps和maps很相似,但是MultiMaps允许重复的元素
函数
ma.begin();// 返回指向第一个元素的迭代器
ma.clear();// 删除所有元素
ma.count() ;//返回一个元素出现的次数
ma.empty();// 如果multimap为空则返回真
ma.end() ;//返回一个指向multimap末尾的迭代器
ma.equal_range();// 返回指向元素的key为指定值的迭代器对
ma.erase();// 删除元素
ma.find();// 查找元素
ma.get_allocator() ;//返回multimap的配置器
ma.insert();// 插入元素
ma.key_comp();// 返回比较key的函数
ma.lower_bound();// 返回键值>=给定元素的第一个位置
ma.max_size() ;//返回可以容纳的最大元素个数
ma.rbegin();// 返回一个指向mulitmap尾部的逆向迭代器
ma.rend();// 返回一个指向multimap头部的逆向迭代器
ma.size();// 返回multimap中元素的个数
ma.swap();// 交换两个multimaps
ma.upper_bound();//返回键值>给定元素的第一个位置
ma.value_comp();//返回比较元素value的函数
集合(set/multiset)
定义
集合(Set)是一种包含已排序对象的关联容器
多元集合(MultiSets)和集合(Sets)相像,只不过支持重复对象。
函数
se.begin();//返回指向第一个元素的迭代器
se.clear();//清除所有元素
se.count();//返回某个值元素的个数
se.empty();//如果集合为空,返回true
se.end();//返回指向最后一个元素的迭代器
se.equal_range();//返回集合中与给定值相等的上下限的两个迭代器
se.erase();//删除集合中的元素
se.find();//返回一个指向被查找到元素的迭代器
se.get_allocator();//返回集合的分配器
se.insert();//在集合中插入元素
se.lower_bound();// 返回指向大于(或等于)某值的第一个元素的迭代器
se.key_comp();//返回一个用于元素间值比较的函数
se.max_size();//返回集合能容纳的元素的最大限值
se.rbegin();//返回指向集合中最后一个元素的反向迭代器
se.rend();// 返回指向集合中第一个元素的反向迭代器
se.size();// 集合中元素的数目
se.swap();// 交换两个集合变量
se.upper_bound();//返回大于某个值元素的迭代器
se.value_comp();//返回一个用于比较元素间的值的函数
例题
约瑟夫问题
题目描述
n 个人围成一圈,从第一个人开始报数,数到 m 的人出列,再由下一个人重新从 1 开始报数,数到 m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
输入格式
输入两个整数 n,m。
输出格式
输出一行 𝑛n 个整数,按顺序输出每个出圈人的编号。
输入输出样例
输入
10 3
输出
3 6 9 2 7 1 8 5 10 4
说明/提示
1≤𝑚,𝑛≤1001≤m,n≤100
思路
把所有数存入一个队列,去队首元素,用一个计数器记录报数,报到倍数就输出,否则放回队列
AC代码
#include<queue>
#include<iostream>
#include<cstdio>
using namespace std;
queue<int>qu;
int n,m;
inline int read()
{
char c = getchar();int x = 0,s = 1;
while(c < '0' || c > '9') {if(c == '-') s = -1;c = getchar();}//快读优化时间
while(c >= '0' && c <= '9') {x = x*10 + c -'0';c = getchar();}
return x*s;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)qu.push(i);
int i=0;
while(qu.size()){
int a=qu.front();
qu.pop();
i++;
if(i%m!=0)qu.push(a);
else printf("%d ",a);
}
return 0;
}
热血格斗场
[题目描述]
为了迎接08年的奥运会,让大家更加了解各种格斗运动,facer新开了一家热血格斗场。格斗场实行会员制,但是新来的会员不需要交入会费,而只要同一名老会员打一场表演赛,证明自己的实力。
我们假设格斗的实力可以用一个正整数表示,成为实力值。另外,每个人都有一个唯一的id,也是一个正整数。为了使得比赛更好看,每一个新队员都会选择与他实力最为接近的人比赛,即比赛双方的实力值之差的绝对值越小越好,如果有两个人的实力值与他差别相同,则他会选择比他弱的那个(显然,虐人必被虐好)。
不幸的是,Facer一不小心把比赛记录弄丢了,但是他还保留着会员的注册记录。现在请你帮facer恢复比赛纪录,按照时间顺序依次输出每场比赛双方的id。
输入
第一行一个数n(0 < n <=100000),表示格斗场新来的会员数(不包括facer)。以后n行每一行两个数,按照入会的时间给出会员的id和实力值。一开始,facer就算是会员,id为1,实力值1000000000。输入保证两人的实力值不同。
输出
N行,每行两个数,为每场比赛双方的id,新手的id写在前面。
样例输入
3 2 1 3 3 4 2
样例输出
2 1 3 2 4 2
思路
就是一道平衡树+模拟
AC代码
#include<iostream>
#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
map<ll, ll> member;
int main() {
member.insert(make_pair(1000000000, 1));
ll n = 0;
//cin >> n;
scanf("%ld", &n);
//cout << n << endl;
ll* rs = new ll[2 * n];
for (ll i = 0; i < n; i++) {
ll id = 0;
ll val = 0;
scanf("%ld %ld", &id, &val);
rs[2 * i] = id;
member.insert(make_pair(val, id));
map<ll, ll>::iterator it = member.find(val);
if (it == member.begin()) {
it++;
int p2 = it->second;
rs[2 * i + 1] = p2;
continue;
}
it--;
int p1 = (it)->second;
int s1 = (it)->first;
it++;
it++;
if (it == member.end()) {
rs[2 * i + 1] = p1;
continue;
}
int p2 = (it)->second;
int s2= (it)->first;
if (abs(val - s1) <= abs(val - s2))
rs[2 * i + 1] = p1;
else
rs[2 * i + 1] = p2;
}
for (ll i = 0; i < n; i++) {
printf("%ld ", rs[2 * i]);
printf("%ld \n", rs[2 * i + 1]);
}
return 0;
}
堆排序
[题目描述]
输入一个长度为 n的整数数列,从小到大输出前 m 小的数。
输入格式
第一行包含整数 n 和 m。
第二行包含 n 个整数,表示整数数列。
输出格式
共一行,包含 m 个整数,表示整数数列中前 m 小的数。
数据范围
1≤m≤n≤105,
1≤数列中元素≤109
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3
思路
把每个数存入堆,逐个输出
由于这道题的时候闲得无聊,就手写了一个堆(见谅)
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int m,n;
int h[N],size;
void down(int u){
int t=u;
if(u*2<=size&&h[u*2]<h[t])t=u*2;
if(u*2+1<=size&&h[u*2+1]<h[t])t=u*2+1;
if(u!=t){
swap(h[u],h[t]);
down(t);
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&h[i]);
size=n;
for(int i=n/2;i>=1;i--)down(i);
while(m--){
printf("%d ",h[1]);
h[1]=h[size];
size--;
down(1);
}
return 0;
}
合并果子
[题目描述]
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。
输入
输入包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。
输出
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于231。
样例输入
3 1 2 9
样例输出
15
思路
不断合并最小的两堆,贪心思路
AC代码
#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
scanf("%d",&n);
priority_queue<int,vector<int>,greater<int> >heap;
for(int i=0;i<n;i++){
int l;
scanf("%d",&l);
heap.push(l);
}
int res=0;
while(heap.size()>1){
int a=heap.top();heap.pop();
int b=heap.top();heap.pop();
res+=a+b;
heap.push(a+b);
}
printf("%d\n",res);
return 0;
}
询问学号
题目描述
有𝑛(𝑛≤2×10^6)n(n≤2×10^6) 名同学陆陆续续进入教室。我们知道每名同学的学号(在 1 到 10^9 之间),按进教室的顺序给出。上课了,老师想知道第 𝑖i 个进入教室的同学的学号是什么(最先进入教室的同学 𝑖=1),询问次数不超过 10^5 次。
输入格式
第一行 2 个整数 n 和 m,表示学生个数和询问次数。
第二行 n 个整数,表示按顺序进入教室的学号。
第三行 m 个整数,表示询问第几个进入教室的同学。
输出格式
输出 m 个整数表示答案,用换行隔开。
输入输出样例
输入
10 3 1 9 2 60 8 17 11 4 5 14 1 5 9
输出
1 8 5
思路
模拟
AC代码
#include <iostream>
#include <vector>
using namespace std;
vector<long > ve;
int main() {
int n, m;
cin >> n >> m;
while (n--) {
long x;
cin >> x;
ve.push_back(x);
}
while (m--) {
int idx;
cin >> idx;
// 注意i从1开始,但下标从0开始,所以要-1
cout << ve[idx - 1] << endl;
}
return 0;
}
验证栈序列
题目描述
给出两个序列 pushed 和 poped 两个序列,其取值从 1 到 n(n≤100000)。已知入栈序列是 pushed,如果出栈序列有可能是 poped,则输出 Yes
,否则输出 No
。为了防止骗分,每个测试点有多组数据,不超过 55 组。
输入格式
第一行一个整数 𝑞q,询问次数。
接下来 𝑞q 个询问,对于每个询问:
第一行一个整数 𝑛n 表示序列长度;
第二行 𝑛n 个整数表示入栈序列;
第三行 𝑛n 个整数表示出栈序列;
输出格式
对于每个询问输出答案。
输入输出样例
输入
2 5 1 2 3 4 5 5 4 3 2 1 4 1 2 3 4 2 4 1 3
输出
Yes No
思路
用栈模拟,最后栈不为空就是错的
AC代码
#include<bits/stdc++.h>
using namespace std;
stack<int>q;
int n,m,t,a[100010],b[100010];
inline void read(int &x)
{
x=0;char c = getchar();int s = 1;
while(c < '0' || c > '9') {if(c == '-') s = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c -'0';c = getchar();}
x*=s;
}
int main(){
read(t);
while(t--){
while(!q.empty())q.pop();
int o=1;
read(n);
for(int i=1;i<=n;i++)read(a[i]);
for(int i=1;i<=n;i++)read(b[i]);
for(int i=1;i<=n;i++){
q.push(a[i]);
while(q.top()==b[o]){
q.pop();
o++;
if(q.empty())break;
}
}
if(q.empty())puts("Yes");
else puts("No");
}
return 0;
}
SET
[题目描述]
现有一整数集(允许有重复元素),初始为空。我们定义如下操作:
add x 把x加入集合
del x 把集合中所有与x相等的元素删除
ask x 对集合中元素x的情况询问
对每种操作,我们要求进行如下输出。
add 输出操作后集合中x的个数
del 输出操作前集合中x的个数
ask 先输出0或1表示x是否曾被加入集合(0表示不曾加入),再输出当前集合中x的个数,中间用空格格开。
输入
第一行是一个整数n,表示命令数。0<=n<=100000。
后面n行命令,如Description中所述。
输出
共n行,每行按要求输出。
样例输入
7 add 1 add 1 ask 1 ask 2 del 2 del 1 ask 1
样例输出
1 2 1 2 0 0 0 2 1 0
提示
Please use STL’s set and multiset to finish the task
思路
用set和multiset同时来维护数据
AC代码
#include<iostream>
#include<algorithm>
#include<list>
#include<set>
using namespace std;
multiset<int>se;
set<int>se1;
int main() {
int n;
cin >> n;
while (n--) {
string str;
cin >> str;
int x;
cin >> x;
switch (str[1]) {
case 'd':
se.insert(x);
se1.insert(x);
cout << se.count(x) << endl;
break;
case 'e':
cout << se.erase(x) << endl;
break;
case 's':
cout << se1.count(x) << " " << se.count(x) << endl;
break;
}
}
}
习题先将这么多,stl的题目有很多,在这里就无法一一列举了
这是我的第四篇文章,如有纰漏也请各位大佬指正
辛苦创作不易,还望看官点赞收藏打赏,后续还会更新新的内容。