数据结构 - 队列

队列的概念

队列是一种先进先出(First In First Out, 简称 FIFO)的数据结构。

队列的概念及基本术语

队头、队尾、入队、出队:队列(Queue)是一种运算受限的特殊线性表。队列是从表的一端(队尾rear)入队,从表的另一端(队头front)出队。

“先进先出”:假设有队列 $Q=(a_1,a_2,a_3,\dots,a_n)$,则队列 Q 中的元素是按 $a_1,a_2,a_3,\dots,a_n$ 的顺序依次入队,也只能按照 $a_1,a_2,a_3,\dots,a_n$ 的顺序依次出队。因此,队列也被称作先进先出线性表(FIFO-First In First Out)。

在入队、出队的过程中,队列呈现如下几种状态:

队空:队列中没有任何元素;

队满:队列空间已全被占用;

溢出:

  1. 当队列已满,却还有元素要“入队”,就出现“上溢(overflow)”;

  2. 当队列已空:却还要做“出队”操作,就出现“下溢(underflow)”。

两种情况合在一起,统称为队列的“溢出”,也是 “真溢出”,后文会提到 “假溢出”

队列的存储

队列可以用数组表示,并设置两个指针:队头指针 front 和队尾指针 rear。

为简化操作,通常约定 front 指向队头元素所在的位置,rear 就指向队尾元素的下一个位置(即 front 就指向队头元素,rear 指向队尾元素的后一个位置)。

队列的操作

定义:

初始化:队头、队尾指针均指向 0。

入队:元素先进入 rear 的位置,然后 rear 再加 1,若 rear == N,表明队列已满。

出队:先取队首(front)元素,再出队(front 加 1),若 front == rear,表明队列已空。

手写队列

#include<iostream>
using namespace std;
const int N=1e6;
struct queue {
    int a[N];
    int rr=0,ff=0;
    void push(int n) { a[rr++]=n; }
    void pop() { if(ff<rr) ff++; }
    bool empty() { return (ff==rr)?0:1; }
    int size() { return rr-ff; }
    int front() { return a[ff]; }
    int back() { return a[rr-1]; }
} q;
int main() {
    q.push(1);//插入元素1 
    q.push(2);//插入元素2 
    q.push(114514);//插入元素114514 
    printf("%d %d %d %d\n",q.empty(),q.front(),q.back(),q.size()); //输出s是否为空、s的顶元素和s的元素个数 
    q.pop();//删除插入的元素 
    printf("%d %d %d %d\n",q.empty(),q.front(),q.back(),q.size()); //输出s是否为空、s的顶元素和s的元素个数 
    q.pop();//删除插入的元素 
    printf("%d %d %d %d\n",q.empty(),q.front(),q.back(),q.size()); //输出s是否为空、s的顶元素和s的元素个数 
    q.pop();//删除插入的元素 
    printf("%d %d\n",q.empty(),q.size()); //再次输出s是否为空和s的素个数
    return 0;
} 

当我们插入 $9\times 10^5$ 个元素,我们把他们都删掉,再加入 $9\times 10^5$ 个元素,此时队列中只有一个元素,但是此时队满了,如果我们再插入一个元素会发现代码 RE 了,队列中数组访问越界,此时,与刚才的"真溢出"不同,因为队列原本是可以容纳 $9\times 10^5$ 个元素,所以称为 “假溢出”

我们做一个循环队列优化,避免这个问题。

循环队列

循环队列的实现思路非常简单,当队尾越界数时,使队尾等于 $0$ 即可,注意此时统计队列长度的方式也要稍加改动。

#include<iostream>
using namespace std;
const int N=1e7;
struct queue {
    int a[N];
    int rr=0,ff=0;
    void push(int n) { 
        a[rr++]=n;
        if(rr==N-1) rr=0;
    }
    void pop() { if(ff!=rr) ff++; }
    bool empty() { return (ff==rr)?0:1; }
    int size() { return (rr-ff+N)%N; }
    int front() { return a[ff]; }
    int back() { return a[rr-1]; }
} q;
int main() {
    q.push(1);//插入元素1 
    q.push(2);//插入元素2 
    q.push(114514);//插入元素114514 
    printf("%d %d %d %d\n",q.empty(),q.front(),q.back(),q.size()); //输出s是否为空、s的顶元素和s的元素个数 
    q.pop();//删除插入的元素 
    printf("%d %d %d %d\n",q.empty(),q.front(),q.back(),q.size()); //输出s是否为空、s的顶元素和s的元素个数 
    q.pop();//删除插入的元素 
    printf("%d %d %d %d\n",q.empty(),q.front(),q.back(),q.size()); //输出s是否为空、s的顶元素和s的元素个数 
    q.pop();//删除插入的元素 
    printf("%d %d\n",q.empty(),q.size()); //再次输出s是否为空和s的素个数
    return 0;
} 

2.STL_queue (懒人的法宝)

queue 中的常用操作:

front():返回 queue 中的第一个元素的引用。

back():返回 queue 中最后一个元素的引用。

push():在 queue 的尾部添加一个元素。

pop():删除 queue 中的第一个元素。

size():返回 queue 中元素的个数。

empty():如果 queue 中没有元素,则返回 true。

代码实现:

#include<iostream>
#include<queue> //一个头文件、方便千万行代码 
using namespace std;
const int N=1e7;
queue<int> s;//声明了一个int类型的队列 
//如果想声明char类型,就写 queue<char> s;
//想声明long long类型,就写 queue<long long> s;
//以此类推 
int main() {
    s.push(1);//插入元素1 
    s.push(2);//插入元素2 
    s.push(114514);//插入元素114514 
    printf("%d %d %d %d\n",s.empty(),s.front(),s.back(),s.size()); //输出s是否为空、s的顶元素和s的元素个数 
    s.pop();//删除插入的元素 
    printf("%d %d %d %d\n",s.empty(),s.front(),s.back(),s.size()); //输出s是否为空、s的顶元素和s的元素个数 
    s.pop();//删除插入的元素 
    printf("%d %d %d %d\n",s.empty(),s.front(),s.back(),s.size()); //输出s是否为空、s的顶元素和s的元素个数 
    s.pop();//删除插入的元素 
    printf("%d %d\n",s.empty(),s.size()); //再次输出s是否为空和s的素个数 
    //注意:stl无法返回空队列的front和back元素,如果这样写就会RE 
    return 0;
} 
 
posted @ 2022-07-13 19:00  abensyl  阅读(2)  评论(0)    收藏  举报  来源
//雪花飘落效果