STL入门 + 刷题(上)

下篇博客地址STL入门 + 刷题(下)_千帐灯无此声的博客-优快云博客

👂 【纯音&吉他】洋溢着青春气息的轻快旋律 - 歌单 - 网易云音乐

听着吉他纯音,看书做题,真是一种享受~

补充:点击链接后,左上角有个提交按钮,在《算法训练营》的网站可以直接提交,而不需要像我一开始,去原OJ注册,搞半天还得审核

目录

🍉通用函数

🍈2.4.1  vector

🍍概念

🍍例题  间谍

🍈2.4.2  stack

🍍概念

🚾图解

🍍例题  Web Navigation

🍈2.4.3  queue

🍍数组模拟队列与queue

🍍概念

🚾图解

🍍例题  Knight Moves

🍈2.4.4  list

🍍概念

🚾图解

🍍例题  士兵队列训练

🍈2.4.5  deque

🍍概念

🚾图解

🍍例题  度度熊学队列

🍈2.4.6  priority_queue

🍍概念

🚾图解

🍍例题  Black Box

📕总结


🍉通用函数

容器通用函数

当然通用函数不一定支持所有的容器,这个需要自己去熟悉

.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_不掉头发程序猿的博客-优快云博客

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

🍍例题  度度熊学队列

度度熊学队列 - HDU 6375 - Virtual Judge (vjudge.net)

先解释下样例

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

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代码要用这个提交

换一个语言会报错

报错的原因是,>>,两个符号连在一起,有右移歧义,所以应该类似这样

priority_queue<int, vector<int>, greater<int> >q2;
//或者
set<int, greater<int> >a; //注意greater<int>后有空格

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支持数组表示

字符串快速读入(读入优化

#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
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千帐灯无此声

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值