👂 【纯音&吉他】洋溢着青春气息的轻快旋律 - 歌单 - 网易云音乐
听着吉他纯音,看书做题,真是一种享受~
补充:点击链接后,左上角有个提交按钮,在《算法训练营》的网站可以直接提交,而不需要像我一开始,去原OJ注册,搞半天还得审核
目录
🍉通用函数
🍈2.4.1 vector
🍍概念
🍍例题 间谍
🍈2.4.2 stack
🍍概念
🚾图解
🍈2.4.3 queue
🍍数组模拟队列与queue
🍍概念
🚾图解
🍍例题 Knight Moves
🍈2.4.4 list
🍍概念
🚾图解
🍍例题 士兵队列训练
🍈2.4.5 deque
🍍概念
🚾图解
🍍例题
🍈2.4.6 priority_queue
🍍概念
🚾图解
🍍例题
📕总结
🍉通用函数
容器通用函数
当然通用函数不一定支持所有的容器,这个需要自己去熟悉
.size() //元素个数
.empty() //为空,返回bool值
.front() //第一个元素
.back() //最后一个元素
.begin() //指向第1个的指针
.end() //指向最后1个的指针
.swap(b) //交换两个容器内容
::iterator //迭代器
迭代器是什么?一个广义的指针,可以是指针,也可以是对其进行类似指针操作的对象
模板使算法独立于数据类型,迭代器使算法独立于容器类型,比如迭代器输出vector的元素:
for(vector<int>::iterator it = a.begin(); it != a.end(); ++it)
cout<<*it<<endl;
🍈2.4.1 vector
🍍概念
vector(向量)是封装了动态数组的顺序容器(Sequence Container)
支持数组表示法和随机访问
使用时需要#include<vector>
1)创建
vector能存放各种类型对象,C++标准类型或结构体类型
vector<int>a;
vector<int>a(100); //元素个数100,所有数初值为0
vector<int>a(10, 666); //元素个数100,所有数初值为666
vector<int>b(a); //b是a的复制
vector<int>b(a.begin()+3, a.end()-3); //复制[a.begin()+3, a.end()-3)区间元素到vector
创建二维数组
vector<int>a[5]; //创建了5个vector, 每个都是一个数组
2)增加
向vector添加元素,可以从尾部 / 中间添加
但是,中间插入效率较低,需要将插入位置之后所有元素后移,时间复杂度O(n)
a.push_back(5); //尾插一个元素5
a.insert(a.begin()+1, 10); //在a.begin()+1指向元素前插入10
a.insert(a.begin()+1, 5, 10); //a.begin()+1前插入5个10
a.insert(a.begin()+1, b.begin(), b.begin()+3); //a.begin()+1前插入b向量区间元素
3)删除
删除尾部元素,或指定元素 / 区间,或者清空向量
a.pop_back(); //删除向量最后一个元素
a.erase(a.begin()+1); //删除指定位置元素
a.erase(a.begin()+3, a.end()-3); //删除区间[first, last)的元素
a.clear(); //清空向量
4)遍历
数组表示法 / 迭代器
for(int i = 0; i < a.size(); ++i)
cout<<a[i]<<"\t";
for(vector<int>::iterator it = a.begin(); it < a.end; ++it)
cout<<*it<<"\t";
5)改变大小
resize可以改变向量大小,若大于当前,填充默认值;若小于,则舍弃后面部分
a.resize(5); //设置向量大小为5,如果当前向量有8个元素,则舍弃后3个
🍍例题 间谍
SPY - HDU 3527 - Virtual Judge (vjudge.net)
本题有3个名单,可用vector数组解决
1)定义4个vector,分别记录3行字符串和答案字符串
2)判断,第2行在第1行出现,但没有在第3行出现,的字符串,添加到答案字符串中
3)如果答案字符串数组,不为空,顺序输出;为空则输出No enemy spy
补充:关于vector中使用#include<algorithm>的find函数
//表示1
if(find(x.begin(), x.end(), 1) != x.end())
cout<<"found"<<endl;
else
cout<<"not found"<<endl;
//表示2
vector<int>::iterator it = find(x.begin(), x.end(), 1);
if(it != x.end())
cout<<"found"<<endl;
else
cout<<"not found"<<endl;
!= x.end()表示找到该元素 --> 因为.end()指向最后一个元素下一位置的指针
!= x.end()说明还没走到结尾,就遇到了1,所以找到了
而如果 == x.end(),表示,直到最后一个元素的下一位置,都没遇到1,所以没找到
补充说明(7条消息) (c++)vector——find方法的使用_c++vector find_不掉头发程序猿的博客-CSDN博客
AC 代码
#include<iostream>
#include<vector>
#include<algorithm> //find()
using namespace std;
vector<string>x, y, z, ans; //声明
int main()
{
int a, b, c;
string s;
while(cin>>a>>b>>c) {//多次输入输出
x.clear(), y.clear(), z.clear(), ans.clear(); //每组测试前清空
for(int i = 0; i < a; ++i) { //读入第1行
cin>>s;
x.push_back(s);
}
for(int i = 0; i < b; ++i) { //读入第2行
cin>>s;
y.push_back(s);
}
for(int i = 0; i < c; ++i) { //读入第3行
cin>>s;
z.push_back(s);
}
//第2行在第1行出现, 但没在第3行出现的字符串
for(int i = 0; i < b; ++i) //按列表b顺序插入
if(find(x.begin(), x.end(), y[i]) != x.end()) //第1行有
if(find(z.begin(), z.end(), y[i]) == z.end()) //第3行没有
ans.push_back(y[i]); //插入答案数组
//输出
if(ans.empty()) //空
cout<<"No enemy spy"<<endl;
else { //非空
for(int i = 0; i < ans.size(); ++i) {
if(i != 0) cout<<" ";
cout<<ans[i];
}
cout<<endl;
}
}
return 0;
}
代码第33行判断非空,等价于
if(!ans.size())
🍈2.4.2 stack
🍍概念
栈(stack)只在栈顶操作,不支持数组表示法,需要头文件#include<stack>
基本操作
//创建空栈s, 数据类型int
stack<int>s;
//x入栈
.push(x);
//出栈
.pop();
//取栈顶元素(未出栈)
.top()
//为空返回true
.empty()
//栈大小, 返回栈中元素个数
.size()
🚾图解
-->-->-->-->-->
1,.push()插入71
2,.push()插入53
3,.push()插入92
4,.top()返回栈顶元素92
5,.pop()删除栈顶元素92
6,.pop()删除栈顶元素53(最后剩一个元素)
🍍例题 Web Navigation
Web Navigation - POJ 1028 - Virtual Judge (vjudge.net)
面向样例编程//
思路
模拟Web浏览器前进和后退的操作,使用两个stack解决,Back表示后向栈,For表示前向栈
1)初始,当前页面cur为***###.acm.org/
2)BACK,后向栈为空,则输出Ignored;否则,cur入前向栈,后向栈顶部页面作为新的cur并弹出,输入新的cur
3)FORWARD,前向栈空,则输出Ignored;否则,cur入后向栈,前向栈顶部成为新cur,弹出 + 输出
4)VISIT,cur放入后向栈顶部,URL作为新的cur,前向栈清空,输出当前cur
5)QUIT,结束程序
代码按照题目描述即可,BACK和FORWARD需要加个判断空,不为空则进行4步操作(注意顺序先后)
VISIT,也是4步操作,一步操作一行代码🆗
书里有4页图解,懒得拍上来了,需要的网上搜搜
AC 代码
#include<iostream>
#include<stack>
using namespace std;
int main()
{
string s, cur = "http://www.acm.org/"; //最初页面
stack<string>Back;
stack<string>For; //forward表示前向栈
while(cin>>s && s != "QUIT") { //输入QUIT结束循环
if(s == "VISIT") {
Back.push(cur); //当前页面入后向栈
cin>>cur; //VISIT后输入新的当前页面
while(!For.empty()) For.pop(); //循环清空前向栈
cout<<cur<<endl; //输出当前页面
}
else if(s == "BACK") {
if(Back.empty()) cout<<"Ignored"<<endl;
else {
For.push(cur); //当前页面入前向栈
cur = Back.top(); //后向栈顶部作为新的页面
cout<<cur<<endl; //输出当前页面
Back.pop(); //后向栈弹出
}
}
else { //当输入FORWARD
if(For.empty()) cout<<"Ignored"<<endl;
else {
Back.push(cur); //cur入后向栈
cur = For.top(); //前向栈顶部作为新cur
cout<<cur<<endl;
For.pop(); //前向栈弹出
}
}
}
return 0;
}
🍈2.4.3 queue
🍍数组模拟队列与queue
第一次接触BFS是《啊哈算法》里的,15年的老书了,当时可能为了普及BFS,用的还是数组模拟队列,现在要学queue了,所以我想对比一下两份代码,在队列实现方面的异同
数组模拟队列
struct note que[2501]; //50*50地图,队列扩展不超2500
int head, tail;
int i, j, k, n, m, startx, starty, p, q, tx, ty, flag;
scanf("%d %d", &n, &m);
for(i = 1; i <= n; ++i)
for(j = 1; j <= m; ++j)
scanf("%d", &a[i][j]);
scanf("%d %d %d %d", &startx, &starty, &p, &q);
//队列初始化
head = 1;
tail = 1;
//往队列插入迷宫入口坐标
que[tail].x = startx;
que[tail].y = starty;
que[tail].s = 0;
que[tail].f = 0;
tail++;
book[startx][starty] = 1;
flag = 0; //标记是否到达目标点,1表示已到达
//当队列不为空时
while(head < tail) {
//枚举四个方向
for(k = 0; k <= 3; ++k) {
tx = que[head].x + next[k][0];
ty = que[head].y + next[k][1];
//判断越界
if(tx < 1 || ty < 1 || tx > n || ty > m)
continue;
//判断不为障碍且未走过
if(a[tx][ty] == 0 && book[tx][ty] == 0) {
//bfs每个点只入队一次
book[tx][ty] = 1;
//插入新的点到队列中
que[tail].x = tx;
que[tail].y = ty;
que[tail].f = head; //本题不用
que[tail].s = que[head].s + 1; //上一步的基础上+1
tail++; //放最后
}
//若达目标点,停止扩展,退出循环
if(tx == p && ty == q) {
flag = 1;
break;
}
}
if(flag) break;
head++; //继续后续点的扩展
}
STL的queue
struct point {
int x, y; // 存储坐标
int step; // 存储到达该点需要的步数
};
point start, node; // 定义结构体变量
node.x = tx;
node.y = ty;
int bfs() {
if (sx == ex && sy == ey) return 0; // 特判
queue<point> Q; // 定义一个队列
Q.push(start); // 将结构体压入队列中
int step, x, y;
while (!Q.empty()) {
start = Q.front(), Q.pop(); // 取队列的头元素,同时把这个元素弹出
x = start.x;
y = start.y;
step = start.step; // 把队列头元素的x,y,step取出
for (int i = 0; i < 8; i++) { // 扩展
tx = x + dx[i];
ty = y + dy[i];
if (tx == ex && ty == ey) return step + 1; // 到达终点,返回最短路径长度
if (tx >= 0 && tx < L && ty >= 0 && ty < L && !vis[tx][ty]) {
node.x = tx; // 类似于数组模拟队列中的q[tail].x
node.y = ty;
node.step = step + 1;
Q.push(node); // 满足条件的进队列
vis[tx][ty] = true; // 标记已访问过
}
}
}
}
对比下,少了tail和head的很多代码(模拟队列),但是都用到了结构体和vis[][]数组,也有循环时队列不为空的判断,还有方向数组的使用
下面借代码具体对比下异同
//判断队列是否为空
// 第一篇代码
while(!Q.empty())
// 第二篇代码
while(head < tail)
//判断起点和终点重合
if (tx == ex && ty == ey)
if(tx == p && ty == q)
🍍概念
队列只能队尾入队,队头出队,不支持数组表示法,需要头文件#include<queue>
基本操作
很多操作类似stack(栈),就.front()有区别
//创建空队列q, 数据类型int
queue<int>q;
//x入队
.push(x);
//出队
.pop(); //和栈加以区分
//取队头(未出队)
.front()
//为空返回true
.empty()
//队列大小, 返回队列中元素个数
.size()
🚾图解
-->-->-->
1,现有队列,队头32,队尾24
2,.push(57)队尾入队
3,.pop()队头32出队
4,.front()取队头元素60
🍍例题 Knight Moves
Knight Moves - POJ 1915 - Virtual Judge (vjudge.net)
在对源码进行认真学习后,第一次耗时1小时,样例对了,但是WA
1,首先注意一个点
//start.s = 0初始化队列, 表示重新从步数0开始扩展
start.x = sx, start.y = sy, start.s = 0;
泪奔.....debug1.5小时,终于过了,真就对着原代码一点一点的删除,增加,修改。。。
处于崩溃边缘(夸张点)....原因在于
2,vis[][]数组每次都想初始化为0
memset(vis, 0, sizeof(vis));
3,队列Q要声明在bfs()函数里,不要声明为全局变量,否则,多组输入下,队列会不断扩展,最终爆队列,毕竟你只设置了maxn = 310,样例过了,因为3组数据都比较小
queue<node>Q; //记得声明在bfs()里!!
AC 代码
#include<iostream>
#include<queue>
#include<cstring> //memset()
using namespace std;
const int maxn = 310; //定义数组大小, 常量const
//n*n地图, 起点sx,sy 终点ex,ey, 当前点tx,ty
int t, n, sx, sy, ex, ey, tx, ty;
struct node
{
int x, y, s; //横坐标, 纵坐标, 步数
};
//方向数组, 8个方向走日
//int dir[8][2] = {1,2,1,-2,-1,2,-1,-2,2,1,2,-1,-2,1,-2,-1};
int dir[8][2]={-2,-1,-2,1,-1,-2,-1,2,1,-2,1,2,2,-1,2,1};
int vis[maxn][maxn]; //标记数组
int bfs()
{
//特判起点 = 终点
if(sx == ex && sy == ey) return 0; //步数为0
//多组测试, 每次都要清空vis[][]数组
memset(vis, 0, sizeof(vis));
queue<node>Q; //定义一个叫Q的队列, 元素类型为结构体node
node start, now; //队头元素和新的元素
//往队列插入起点坐标
//start.s = 0初始化队列, 表示重新从步数0开始扩展
start.x = sx, start.y = sy, start.s = 0;
Q.push(start);
vis[sx][sy] = 1; //标记起点
//当队列不为空
while(!Q.empty()) {
//枚举8个方向
for(int k = 0; k < 8; ++k) {
tx = Q.front().x + dir[k][0];
ty = Q.front().y + dir[k][1];
//到达目标点
if(tx == ex && ty == ey) return Q.front().s + 1;
//未越界且未访问过
if(tx >= 0 && tx < n && ty >= 0 && ty < n && !vis[tx][ty]) {
//bfs每个点只入队一次, 不需要取消标记
vis[tx][ty] = 1; //标记
//插入新的点到队列
now.x = tx, now.y = ty, now.s = Q.front().s + 1;
Q.push(now);
}
}
//队头出队, 才能继续后续点扩展
Q.pop();
}
}
int main()
{
cin>>t;
while(t--){
cin>>n>>sx>>sy>>ex>>ey;
cout<<bfs()<<endl;
}
return 0;
}
🍈2.4.4 list
🍍概念
list是双向链表,常数时间内,插入和删除,不支持数组表示,需要#include<list>
基本操作
专用函数
//链表b与原链表合并,合并前已排序,合并后,b被保存在原链表,b为空
merge(b)
//删除val所有节点
remove(val)
//链表b内容插入pos前,b为空
splice(pos, b)
//链表翻转
reverse()
//排序
sort()
//连续相同元素压缩为单个,一般先排序后去重
unique()
其他函数
//x头插或尾插
push_front(x)
push_back(x)
//链表头或尾出
pop_front()
pop_back()
//p之前插入t
insert(p, t)
//删除p
erase(p)
//清空链表
clear()
辅助理解:http://t.csdn.cn/O1rQI
🚾图解
-->-->
1,初始,双向链表有3个元素
2,.insert(1, 77)在 索引1 的位置插入77
3,.erase(2)删除索引2位置的元素6
🍍例题 士兵队列训练
士兵队列训练问题 - HDU 1276 - Virtual Judge (vjudge.net)
AC 代码
#include<iostream>
#include<list> //双向链表
using namespace std;
int t, n;
list<int>::iterator it; //迭代器
int main()
{
cin>>t;
list<int>a; //声明双向链表list
while(t--) {
cin>>n;
a.clear(); //清空链表
for(int i = 1; i <= n; ++i)
a.push_back(i); //存入士兵编号
int k = 2; //第一次删除喊2的士兵
while(a.size() > 3) {
int cnt = 1; //count计数
for(it = a.begin(); it != a.end();) { // !=
if(cnt++ % k == 0)
//删除该士兵
it = a.erase(it); //it指向下一位士兵的地址
else
it++; //it指向下一位的地址
}
k = (k == 2 ? 3 : 2); //A ? B : C, 如果A为真赋值B, 否则赋值C
}
//退出while循环后, 不到4个士兵, 迭代器遍历输出
for(it = a.begin(); it != a.end(); ++it) { // !=
if(it != a.begin()) cout<<" ";
cout<<*it;
}
cout<<endl;
}
return 0;
}
🍈2.4.5 deque
🍍概念
deque是双端队列,可以在两端进出队,支持数组表示
当需要在两端操作时,用deque,需要头文件#include<deque>
基本操作
//队头或队尾入队
.push_front(x)
.push_back(x)
//队头或队尾出队
.pop_front()
.pop_back()
//返回队头或队尾元素
.front()
.back()
//元素个数
.size()
//空
.empty()
//清空双端队列
.clear()
🚾图解
->->->
1,初始,18队头(索引0),57队尾(索引3)
2,.back()返回队尾元素57
3,.pop_front()队头的18出队
4,.push_back(1)队尾入队1
🍍例题
先解释下样例
2 10,2个队列,10行输入
1 1 1 23,1表示入队,1表示队列1,1表示队尾,23入队
2 1 1,2表示出队,1表示队列1,1表示队尾出队(当队列1为空,输出-1;不为空则返回出队元素)
3 1 2 1,3表示两个队列连接,1表示队列1在前,2表示队列2在后,1表示后一个队列翻转后接在前一个队列后面
读入优化(快速读入)
#include <iostream>
using namespace std;
void read(int &x){
char ch=getchar(); // 从标准输入信息流stdin中读取一个字符ch
x=0; // 将变量x初始化为0
for(;ch<'0'||ch>'9';ch=getchar()); // 如果ch不是数字字符,则跳过该字符并继续读取下一个字符
for(;ch>='0'&&ch<='9';ch=getchar())
{
x=x*10+ch-'0'; // 如果ch是数字字符,则将其转换为对应的整数值,并将结果存储到形参x所指向的变量中
}
}
int main()
{
int n;
read(n); // 调用read函数读入一个整数n
cout<<"读入的整数n为:"<<n<<endl; // 输出n的值
return 0;
}
它会跳过不是数字的,包括空格,字母,特殊字符等,并在读取完第一次连续的数字后结束
jsj &%&^ 21893 213 2 66
读入的整数n为:21893
读取了用户输入的整数,并将该整数存储到了变量
n
中
下面专门解释下引用传参
//引用传参, 不需要额外的返回值, 比如
void addOne(int &x) {
x++;
}
int n = 10;
addOne(n);
//此时n == 11, 其中并没有用到返回值
再看看读入优化中的引用传参
//函数名read, 返回值类型void, 参数int &x
//int &x表示传入类型为int类型引用变量
void read(int &x)
int n;
read(n);
//函数内部对 x 的修改也会同时作用于变量 n
思路
其实本题用双向链表list更好做,而且,由于双端队列deque不支持翻转和拼接,用list的时空复杂度更低,当然下面我两种做法都会实现
1)定义一个deque数组d[] (或者一个list数组d[])
2)判断分别执行3种操作,第2种需要输出
3)第3种情况,list支持.reverse()翻转,而且拼接函数.splice可以将另一个链表v拼接到当前链表的pos位置,并自动清空v
而deque不支持翻转,所以考虑用反向迭代器控制,.insert配合迭代器插入
list 翻转 + 拼接
d[v].reverse(); //翻转
d[u].splice(d[u].end(), d[v]); //拼接
deque 反向 + 插入
d[u].insert(d[u].end(), d[v].rbegin(), d[v].rend()); //反向着插入
d[u].insert(d[u].end(), d[v].begin(), d[v].end()); //正向着插入
d[v].clear(); //清空队列
Debug
1,快速读入中,第一个for要加 ; (分号) 否则会作用于下一个for
AC 代码 list 双向链表
#include<iostream>
#include<list> //双向链表
#include<cstdio> //printf()
const int maxn = 15e4 + 10;
using namespace std;
list<int> d[maxn];
void read(int &x) //传入类型为int的引用变量
{
char ch = getchar(); x = 0; //读取一个字符
//第1个for要加分号, 否则会作用于下一个for
for(; ch < '0' || ch > '9'; ch = getchar()); //ch不是数字字符就跳过
for(; ch >= '0' && ch <= '9'; ch = getchar()) {
x = x * 10 + ch - '0';
}
}
//ch是数字字符, 转换为整数值, 存储到形参x所指向的变量中
int main()
{
int n, m;
while(~scanf("%d%d", &n, &m)) { //多组输入
for(int i = 1; i <= n; ++i)
d[i].clear(); //初始化链表
int k, u, v, w;
while(m--) { //m行输入
read(k); //读入字符的整型数值存储到k中
switch(k) {
case 1:
read(u), read(w), read(v); //读入3个空格分开的数字
if(w == 0) d[u].push_front(v);
else d[u].push_back(v);
break;
case 2:
read(u), read(w);
if(d[u].empty()) printf("-1\n");
else {
if(w == 0) {
printf("%d\n", d[u].front()); //先输出
d[u].pop_front(); //再删除
}
else {
printf("%d\n", d[u].back());
d[u].pop_back();
}
}
break;
case 3:
read(u), read(v), read(w);
if(w)
d[v].reverse(); //后一个链表翻转
d[u].splice(d[u].end(), d[v]); //拼接函数splice会自动清空v, 时间复杂度常数
break;
}
}
}
return 0;
}
!AC 代码 deque 双向队列
list的基础上,稍作修改
Memory Limit Exceeded !
来分析下,首先,不论list双向链表还是deque双向队列,头尾插入,删除元素的时间复杂度都是O(1),但是deque不支持翻转和拼接,靠反向迭代器和insert的话,依次插入每个元素,就会内存超限?我也不是很懂原理
#include<iostream>
#include<deque> //双向队列
#include<cstdio> //printf()
const int maxn = 15e4 + 10;
using namespace std;
deque<int> d[maxn];
void read(int &x) //传入类型为int的引用变量
{
char ch = getchar(); x = 0; //读取一个字符
//第1个for要加分号, 否则会作用于下一个for
for(; ch < '0' || ch > '9'; ch = getchar()); //ch不是数字字符就跳过
for(; ch >= '0' && ch <= '9'; ch = getchar()) {
x = x * 10 + ch - '0';
}
}
//ch是数字字符, 转换为整数值, 存储到形参x所指向的变量中
int main()
{
int n, m;
while(~scanf("%d%d", &n, &m)) { //多组输入
for(int i = 1; i <= n; ++i)
d[i].clear(); //初始化队列
int k, u, v, w;
while(m--) { //m行输入
read(k); //读入字符的整型数值存储到k中
switch(k) {
case 1:
read(u), read(w), read(v); //读入3个空格分开的数字
if(w == 0) d[u].push_front(v);
else d[u].push_back(v);
break;
case 2:
read(u), read(w);
if(d[u].empty()) printf("-1\n");
else {
if(w == 0) {
printf("%d\n", d[u].front()); //先输出
d[u].pop_front(); //再删除
}
else {
printf("%d\n", d[u].back());
d[u].pop_back();
}
}
break;
case 3:
read(u), read(v), read(w);
if(w)
d[u].insert(d[u].end(), d[v].rbegin(), d[v].rend());
else
d[u].insert(d[u].end(), d[v].begin(), d[v].end());
d[v].clear(); //清空队列
break;
}
}
}
return 0;
}
书里的代码内存超限啊👆
🍈2.4.6 priority_queue
🍍概念
priority_queue是优先队列,优先级高的最先出队,默认最大值优先
内部实现为二叉堆,So,出队入队时间O(logn)
可定义最大值优先队列 或 最小值优先队列
//默认的优先队列,最大值先出队,通过大根堆实现
priority_queue<int>q1;
//最小值优先队列,最小值先出队,小根堆实现
priority_queue<int, vector<int>, greater<int>>q2;
模板
priority_queue<T,Sequence,Compare>
解释
T:存放在容器中元素的类型,比如int
Sequence:实现优先队列的底层容器,默认是vector<T>,比如vector<int>
Compare:实现优先级的比较函数,默认less<T>,最大值优先队列
优先队列不支持删除堆中指定元素,只可删除堆顶元素,需要#include<queue>
🚾图解
1)
初始
2)
.push(88)将88插入,88 > 39,88和39交换
3)
.push(27)将27插入,27 < 88但 27 > 2,27和2交换
4)
.push(31)插入31,31 < 88,31 > 27,31和27交换
5)
.top()取队头88,.pop()堆顶元素88出队,88出队后....(因为是二叉堆实现的优先队列,就不具体解释二叉堆过程了,毕竟和STL的priority_queue也是有点功能上的区别的)
🍍例题
Black Box - POJ 1442 - Virtual Judge (vjudge.net)
看中文题面,都看了好一会才看明白,慢慢来吧,别人1小时能AC的题,你就算花2~3小时才AC,那也是AC
面向样例编程
不得不说,书里的代码真的烂,不好懂,只能另外从网上找了个好理解的思路
思路
创建2个priority_queue,最大值优先队列q1优先弹出最大的,最小值优先队列q2优先弹出最小的
要求第 i 小的数字,只需满足 q1 里有 i 个元素,且 q1.top() < q2.top(),则 q1.top()就是第 i 小的数字
通俗理解,就是 i 个元素里最大的那个,比剩下元素里最小的,还小
那么这 i 个元素里最大的,就是第 i 小的数字,比如
q1 -- 8 4 2 1 (最大值优先队列)
q2 -- 11 19 (最小值优先队列)
q1里有4个元素,那么8就是第4小的元素,也就是题目要求输出的
需要注意的是,下面的AC代码要用这个提交
换一个语言会报错
AC 代码
#include<iostream>
#include<queue>
#include<cstdio> //scanf(), printf()
const int maxn = 3e4 + 10;
//#define maxn 3e4 + 10
using namespace std;
int a[maxn], b[maxn]; //输入的序列
int main()
{
priority_queue<int>q1; //最大值优先队列
priority_queue<int, vector<int>, greater<int>>q2; //最小值优先队列
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]); //输入ADD
for(int i = 1; i <= m; ++i)
scanf("%d", &b[i]); //输入GET
//遍历
int cnt = 1; //输入到第count个数
for(int i = 1; i <= m; ++i) { //按样例1 2 6 6 遍历
//先加入最小值优先队列q2
for(; cnt <= b[i]; ++cnt) //<=
q2.push(a[cnt]);
//要求第i小, 最大值优先队列要有 i 个数
while(q1.size() < i) {
q1.push(q2.top()); //转移
q2.pop();
}
//q2不为空 且 q1.top() > q2.top()就交换
while(!q2.empty() && q1.top() > q2.top()) {
int temp = q1.top();
q1.pop();
q1.push(q2.top()); //q1堆顶插入q2元素
q2.pop();
q2.push(temp); //q2堆顶插入q1元素
}
printf("%d\n", q1.top()); //输出第 i 小元素
}
return 0;
}
📕总结
上面6种stl数据结构的适用场景和基本函数各不相同,下面加以区分
通用操作
.size() //元素个数
.empty() //为空,返回bool值
.front() //第一个元素
.back() //最后一个元素
.begin() //指向第1个的指针
.end() //指向最后1个的指针
.swap(b) //交换两个容器内容
::iterator //迭代器
注意这里的.front()和.back()主要支持queue和deque
迭代器
for(vector<int>::iterator it = a.begin(); it != a.end(); ++it)
cout<<*it<<endl;
🔥1,双向链表list 类似 双端队列deque,都有的操作是
list常数时间内插入删除,deque是个双端的队列
.push_front(x)
.push_back(x)
.pop_front()
.pop_back()
.size()
.empty()
.clear()
但是deque多了.front()和.back()
list可以合并.merge(), 拼接.splice(), 翻转.reverse(), .sort()排序后.unique()去重
中间插入.insert(p, t), 删除.erase(p) -->注意这里的p是指向p节点的迭代器,而非索引或者值
deque则多了个.front, .back()
🔥2,栈stack 类似 队列queue,都有的操作是
stack只能从栈顶存取,而queue只能队尾进,队头出
.push(x);
.pop();
.empty()
.size()
stack是.top()返回栈顶,queue是.front()返回队头
3,vector可以理解成特殊的数组,priority_queue是特殊的队列
vector操作
vector<int>a;
vector<int>a(100);
vector<int>a(10, 666);
vector<int>b(a);
vector<int>b(a.begin()+3, a.end()-3);
二维数组
vector<int>a[5]; //创建了5个vector, 每个都是一个数组
vector尾部操作较快,中间操作较满,以下是基本操作
a.push_back(5);
a.insert(a.begin()+1, 10);
a.insert(a.begin()+1, 5, 10); //a.begin()+1前插入5个10
a.insert(a.begin()+1, b.begin(), b.begin()+3);
a.pop_back();
a.erase(a.begin()+1);
a.erase(a.begin()+3, a.end()-3); //删除[first, last)
a.clear();
for(int i = 0; i < a.size(); ++i)
cout<<a[i]<<"\t"; //等价于迭代器
for(vector<int>::iterator it = a.begin(); it < a.end; ++it)
cout<<*it<<"\t";
a.resize(5); //多删少补
最后提一嘴,6个容器里,只有vector和deuqe支持数组表示