【数据结构】队列的基本概念与操作

目录

队列的基本概念

顺序队列

循环队列

定义

基本操作

初始化队列

判队空

入队

出队

链式队列

基本操作

初始化

判队空

入队

出队

双端队列

队列的注意点


队列的基本概念

队列(Queue),简称“队”,是一种操作受限的线性表,其特点在于只允许在一端进行插入,而在另一端进行删除。将元素加入队列称为入队,而从队列中移除元素称为出队。

这一特性与我们日常生活中的排队方式类似——最先进入队列的元素最先被移除。队列遵循先进先出(First In, First Out,FIFO)的原则。

队列结构如下图所示:

其中几个重要概念如下:

队头(Front):允许删除的一端,又称队首。
队尾(Rear):允许插入的一端。
空队列:不含任何元素的空表。

顺序队列

队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:

队头指针(front):指向队头元素

队尾指针(rear):指向队尾元素的下一个位置

但是更一般的情况下队头队尾指针不一定这样指向,在本文的最后一部分会详细说明。

顺序队列的结构体定义如下:

#define MaxSize 4            //队列最大存储容量
    typedef struct{
    int data[MaxSize-1];     //注意MaxSize-1,部分教材这里用的MaxSize,会混淆
    int front, rear;
}SqQuene;

开始时Q.front=Q.rear=0,入队时队尾指针记录入队值后+1(因为队尾指针指向队尾元素的下一个位置。),出队时记录出队值后队头指针+1

由于入队出队指针都是+1,可以预见的是如果5个元素入队并且出队,队头队尾指针都变成5,此时指针位置超出队列限值,出现“假溢出”情况,即队列里明明什么都没有但是指针还是超限。

于是这里给出循环队列的概念。

循环队列

定义

部分教材上将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。当指针到达队列末端,且还要再将一个元素入队时,将指针重置为数组的第一位,也就是跟个圆圈一样将数组头尾相连。

要实现这样的操作,可以使用计算机里专门的操作符——求余运算(%)。当队首指针需要进1时,令Q.front=(Q.front+1)%MaxSize;当队尾指针需要进1时,令Q.rear=(Q.rear+1)%MaxSize;

注意这里MaxSize为最大存储容量,有的教材前面代码定义部分与这里冲突了。

但有一个问题是,按照上面这种方式,队满队空时都有Q.front==Q.rear。于是下面给出一种常见的处理这种问题的方法:牺牲一个存储单元来区分队空队满。

当队满时,判定条件为(Q.rear+1)%Maxsize==Q.front。

当然,从做题的角度来说我更喜欢把循环队列视为一个具有“虫洞”的队列。当队的一头进入到队列存储结构的末端时,自动从队列的头部钻出。

其实很容易看出,Q.rear的值一定会比Q.front更大,如果Q.front大于Q.rear,说明队尾指针已经从“虫洞”钻出。

当然如果你把队列视作一个圆,那么队尾一定会在队头的顺时针方向(即存储的元素位于front顺时针到rear的环内)。

队列长度的通解公式为(Q.rear+MaxSize-Q.front)%MaxSize。

基本操作

初始化队列
void InitQuene(SqQuene &Q){
    Q.front=Q.rear=0;                //头尾指针置零
}
判队空
bool IsEmpty(SqQuene Q){
    if(Q.rear==Q.front)        //队头队尾指针相等时队列为空
        return true;
    else
        return false;
}
入队
bool EnQuene(SqQuene &Q,int x){
    if((Q.rear+1)%MaxSize==Q.front)        //队满报错
        return false;
    Q.data[Q.rear]=x;                      //赋值
    Q.rear=(Q.rear+1)%MaxSize;             //重置尾指针
    return true;
}
出队
bool DeQuene(SqQuene &Q,int &x){
    if(Q.rear==Q.front)
        return false;                //队空判错
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize
    return true;
}

链式队列

队列的链式表示称为链队列,它实际上是一个同时有队头指针和队尾指针的非循环单链表(当然带尾指针的循环单链表也可以表示队列【数据结构】链表的基本概念和操作)。

链式队列的定义如下:

typedef struct LinkNode{            //定义队列节点
    int data;
    struct LinkNode *next           //指针域
}LinkNode;        
typedef struct{                     //定义队列
    LinkNode *front,*rear;          //队头队尾
}LinkQuene;

这里需要注意链式队列的定义使用了两个结构体,一个结构体表示队列节点单元,另一个表示队列头尾指针。那么为什么链栈只需要使用一个结构体呢?这是因为链栈只需要一个top指针就够操作了,而top指针就是链表的头指针,而一个链表的头指针L在初始化的时候就定死了;但是队列需要尾指针,如果尾指针域定义在节点结构体里,像下面这样:

typedef struct LinkNode{            //定义队列节点
    int data;
    struct LinkNode *next,*rear;           //指针域与尾结点域
}LinkNode;        

那么除了最后一个节点外的每一个节点都会浪费一个指针域。所以我们需要另起一个LinkQuene结构体来单独区分头尾指针。

基本操作

初始化

void InitLQuene(LinkQuene &Q){
    Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkeNode));        //头结点
    Q.front->next=nullptr;                                      //置空
}                            

判队空

bool IsEmpty(LinkQueueQ){
    if(Q.front==Q.rear)
        return true;
    else
        return false;
}

入队

void EnLQuene(LinkQuene *Q,int x){
    LinkNode *q=(LinkNode*)malloc(sizeof(LinkNode));    //创建新结点
    q->data=x;
    q->next=nullptr;
    Q.rear->next=q;             //插入链尾
    Q.rear=q;                   //重置尾指针
}

出队

bool DeQuene(LinkQuene &Q,int &x){
    if(Q.rear==Q.front)                //判队空
        return false;
    LinkNode *s=Q.front->next;
    x=p->data;
    Q.front->next=p->next;             //断剑改连
    if(Q.rear==p)
        Q.rear=Q.front;                //如果只有一个元素,重置尾指针
    free(p);
    return true;
}

这里需要说明一下为什么只有一个元素的时候需要重置尾指针。因为如果只有一个元素,那么尾指针将会指向这个唯一的元素,如果你直接给他free掉了,那么尾指针会变成野指针。

双端队列

双端队列是指允许两端都可以进行插入和删除操作的线性表。双端队列两端的地位是平等的,为了方便理解,将左端也视为前端,右端也视为后端。

有些时候为了某些需求会删除某一端的插入或删除功能,这样的双端队列称为输入受限或者输出受限的双端队列。

队列的注意点

经常会有题目将front指针和rear指针指向的位置胡乱设定(这篇文章使用的都是front指向队首元素,rear指向队尾元素+1),然后问你该队列的判空条件是什么,或者问front与rear的初值是什么。下面给出这种题的通解:

  1. 假设队列里入队了一个元素。
  2. 由于入队后rear+1,front不变,倒推队列为空时front和rear的初值。

 例题:

1、假设用 A[0..n]实现循环队列,front、rear 分别指向队首元素的前一个位置队尾元素。若用(rear+1)%(n+1)==front作为队满标志,则()

A.可用front==rear作为队空标志

B.队列中最多可有n+1个元素

C.可用front>rear作为队空标志
D.可用(front+1)号(n+1)==rear作为队空标志

若A里有一个元素,则front=n(0的前一位), rear=0。又因为该元素入队后rear才等于0,所以没入队之前rear=n。故队空时front=rear。选A。

2、已知循环队列存储在一维数组 A[0..n-1]中,且队列非空时 front和 rear 分别指向队头元素队尾元素。若初始时队列为空,且要求第一个进入队列的元素存储在A[0]处,则初始时front和rear的值分别是()。
A. 0,0
B.0,n-1
C. n-1,0
D.n-1,n-1

若A里有一个元素,则front=0,rear=0。又因为该元素入队后rear才等于0,所以没入队之前rear=n-1。故队空时front=0,rear=n-1。选B。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值