STL入门 + 刷题(上)

STL入门 + 刷题(上)_第1张图片

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

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

补充:点击链接后,左上角有个提交按钮,在《算法训练营》的网站可以直接提交,而不需要像我一开始,去原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::iterator it = a.begin(); it != a.end(); ++it)
    cout<<*it<

2.4.1  vector

概念

vector(向量)是封装了动态数组的顺序容器(Sequence Container)

支持数组表示法和随机访问

使用时需要#include

1)创建

vector能存放各种类型对象,C++标准类型或结构体类型

vectora;
vectora(100); //元素个数100,所有数初值为0
vectora(10, 666); //元素个数100,所有数初值为666
vectorb(a); //b是a的复制
vectorb(a.begin()+3, a.end()-3); //复制[a.begin()+3, a.end()-3)区间元素到vector

创建二维数组

vectora[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<::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)

STL入门 + 刷题(上)_第2张图片

本题有3个名单,可用vector数组解决

1)定义4个vector,分别记录3行字符串和答案字符串

2)判断,第2行在第1行出现,但没有在第3行出现,的字符串,添加到答案字符串中

3)如果答案字符串数组,不为空,顺序输出;为空则输出No enemy spy

补充:关于vector中使用#include的find函数

//表示1
if(find(x.begin(), x.end(), 1) != x.end())
    cout<<"found"<::iterator it = find(x.begin(), x.end(), 1);
if(it != x.end())
    cout<<"found"<

!= x.end()表示找到该元素  --> 因为.end()指向最后一个元素下一位置的指针

!= x.end()说明还没走到结尾,就遇到了1,所以找到了

而如果 == x.end(),表示,直到最后一个元素的下一位置,都没遇到1,所以没找到

补充说明(7条消息) (c++)vector——find方法的使用_c++vector find_不掉头发程序猿的博客-CSDN博客

AC  代码

#include
#include
#include //find()
using namespace std;

vectorx, 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"<

代码第33行判断非空,等价于

if(!ans.size())

2.4.2  stack

概念

栈(stack)只在栈顶操作,不支持数组表示法,需要头文件#include

基本操作

//创建空栈s, 数据类型int
stacks;
//x入栈
.push(x);
//出栈
.pop();
//取栈顶元素(未出栈)
.top()
//为空返回true
.empty()
//栈大小, 返回栈中元素个数
.size()

图解

STL入门 + 刷题(上)_第3张图片-->STL入门 + 刷题(上)_第4张图片-->STL入门 + 刷题(上)_第5张图片-->STL入门 + 刷题(上)_第6张图片-->STL入门 + 刷题(上)_第7张图片-->STL入门 + 刷题(上)_第8张图片

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)

面向样例编程//

STL入门 + 刷题(上)_第9张图片

思路

模拟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
#include
using namespace std;

int main()
{
    string s, cur = "http://www.acm.org/"; //最初页面
    stackBack;
    stackFor; //forward表示前向栈
    while(cin>>s && s != "QUIT") { //输入QUIT结束循环
        if(s == "VISIT") {
            Back.push(cur); //当前页面入后向栈
            cin>>cur; //VISIT后输入新的当前页面
            while(!For.empty()) For.pop(); //循环清空前向栈
            cout<

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 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

基本操作

很多操作类似stack(栈),就.front()有区别

//创建空队列q, 数据类型int
queueq;
//x入队
.push(x);
//出队
.pop(); //和栈加以区分
//取队头(未出队)
.front()
//为空返回true
.empty()
//队列大小, 返回队列中元素个数
.size()

图解

STL入门 + 刷题(上)_第10张图片-->STL入门 + 刷题(上)_第11张图片-->STL入门 + 刷题(上)_第12张图片-->STL入门 + 刷题(上)_第13张图片

1,现有队列,队头32,队尾24

2,.push(57)队尾入队

3,.pop()队头32出队

4,.front()取队头元素60

例题  Knight Moves

Knight Moves - POJ 1915 - Virtual Judge (vjudge.net)

STL入门 + 刷题(上)_第14张图片

在对源码进行认真学习后,第一次耗时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组数据都比较小

queueQ; //记得声明在bfs()里!!

AC  代码

#include
#include
#include //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));
    
    queueQ; //定义一个叫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<

2.4.4  list

概念

list是双向链表,常数时间内,插入和删除,不支持数组表示,需要#include

基本操作

专用函数

//链表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

图解

STL入门 + 刷题(上)_第15张图片-->STL入门 + 刷题(上)_第16张图片-->STL入门 + 刷题(上)_第17张图片

1,初始,双向链表有3个元素

2,.insert(1, 77)在 索引1 的位置插入77

3,.erase(2)删除索引2位置的元素6 

例题  士兵队列训练

士兵队列训练问题 - HDU 1276 - Virtual Judge (vjudge.net)

STL入门 + 刷题(上)_第18张图片

AC  代码

#include
#include //双向链表
using namespace std;

int t, n;
list::iterator it; //迭代器

int main()
{
    cin>>t;
    lista; //声明双向链表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<

2.4.5  deque

概念

deque是双端队列,可以在两端进出队,支持数组表示

当需要在两端操作时,用deque,需要头文件#include

基本操作

//队头或队尾入队
.push_front(x)
.push_back(x)
//队头或队尾出队
.pop_front()
.pop_back()
//返回队头或队尾元素
.front()
.back()
//元素个数
.size()
//空
.empty()
//清空双端队列
.clear()

图解

STL入门 + 刷题(上)_第19张图片->STL入门 + 刷题(上)_第20张图片->STL入门 + 刷题(上)_第21张图片->STL入门 + 刷题(上)_第22张图片

1,初始,18队头(索引0),57队尾(索引3)

2,.back()返回队尾元素57

3,.pop_front()队头的18出队

4,.push_back(1)队尾入队1

例题  度度熊学队列

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

STL入门 + 刷题(上)_第23张图片

先解释下样例

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 
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为:"<

它会跳过不是数字的,包括空格,字母,特殊字符等,并在读取完第一次连续的数字后结束

  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
#include //双向链表
#include //printf()
const int maxn = 15e4 + 10;
using namespace std;

list 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
#include //双向队列
#include //printf()
const int maxn = 15e4 + 10;
using namespace std;

deque 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_queueq1;
//最小值优先队列,最小值先出队,小根堆实现
priority_queue, greater>q2;

模板

priority_queue

解释

T:存放在容器中元素的类型,比如int

Sequence:实现优先队列的底层容器,默认是vector,比如vector

Compare:实现优先级的比较函数,默认less,最大值优先队列

优先队列不支持删除堆中指定元素,只可删除堆顶元素,需要#include

图解

STL入门 + 刷题(上)_第24张图片1)

初始

STL入门 + 刷题(上)_第25张图片STL入门 + 刷题(上)_第26张图片2)

.push(88)将88插入,88 > 39,88和39交换

STL入门 + 刷题(上)_第27张图片STL入门 + 刷题(上)_第28张图片3)

.push(27)将27插入,27 < 88但 27 > 2,27和2交换

STL入门 + 刷题(上)_第29张图片STL入门 + 刷题(上)_第30张图片4)

.push(31)插入31,31 < 88,31 > 27,31和27交换

STL入门 + 刷题(上)_第31张图片STL入门 + 刷题(上)_第32张图片STL入门 + 刷题(上)_第33张图片STL入门 + 刷题(上)_第34张图片STL入门 + 刷题(上)_第35张图片5)

.top()取队头88,.pop()堆顶元素88出队,88出队后....(因为是二叉堆实现的优先队列,就不具体解释二叉堆过程了,毕竟和STL的priority_queue也是有点功能上的区别的)

例题  Black Box

Black Box - POJ 1442 - Virtual Judge (vjudge.net)

STL入门 + 刷题(上)_第36张图片

看中文题面,都看了好一会才看明白,慢慢来吧,别人1小时能AC的题,你就算花2~3小时才AC,那也是AC

STL入门 + 刷题(上)_第37张图片面向样例编程

不得不说,书里的代码真的烂,不好懂,只能另外从网上找了个好理解的思路

思路

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

换一个语言会报错

STL入门 + 刷题(上)_第38张图片

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

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

AC  代码

#include
#include
#include //scanf(), printf()
const int maxn = 3e4 + 10;
//#define maxn 3e4 + 10
using namespace std;
int a[maxn], b[maxn]; //输入的序列

int main()
{
    priority_queueq1; //最大值优先队列
    priority_queue, greater>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::iterator it = a.begin(); it != a.end(); ++it)
    cout<<*it<

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操作

vectora;
vectora(100); 
vectora(10, 666); 
vectorb(a); 
vectorb(a.begin()+3, a.end()-3); 

二维数组

vectora[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<::iterator it = a.begin(); it < a.end; ++it)
    cout<<*it<<"\t";

a.resize(5); //多删少补

最后提一下,6个容器里,只有vector和deuqe支持数组表示

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

#include 
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为:"<
  jsj &%&^ 21893 213 2  66
读入的整数n为:21893

你可能感兴趣的:(《算法训练营入门篇》,c++,算法,数据结构)