栈与队列->队列

本文详细介绍了队列这一数据结构,包括其基本概念、特点、应用场景以及两种主要的存储方式——顺序存储和链式存储。重点讲解了循环队列如何解决传统队列存在的问题,并对比了循环队列与链队列的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

队列(queue)

只允许在一端进行插入操作,而在另外一端进行删除操作的线性表。队列是一种先进先出(First In First Out)的线性表,简称FIFO.允许插入的一端称为队尾,允许删除的一端称为队头。如果队列是q=(a1,a2,a3.....,an),那么a1是队头元素,an为队尾元素。删除时从a1开始,插入时列在an后面。


循环队列

线性表有顺序存储和链式存储,栈是线性表,所以其有这两种方式,同样,队列作为一种特殊的线性表,同样存在这两种模式。


队列顺序存储的不足:

如果我们有一个n个元素的队列,则顺序存储队列需要简历一个大于n的数组,并把队列的所有元素存储在数组的前n的单元。数组下标为0的一端为队头。

入队操作,其实是在队尾添加元素,不需要移动元素,则时间复杂度为O(1)。

出队操作,队列的出列是在队头,即在0的位置,也就以为着队列中所有元素都得向前移动,以保证队头位置不为空,则其时间复杂度O(n)为了避免位置移动,可以引入两个指针,front指向队头元素,rear指针指向队尾元素的下一个位置,这样当front和rear相等时,队列为空。

这样会引入另外的一个问题,在一个有限的队列中加入元素,直到满,头部移除2个元素,front指针后移2次,再加入元素,会产生数组越界错误,可是实际上队列后还有一个位置空闲(0位置),把这种现象称为“假溢出”


循环队列解决了这个问题,我们把队列的头尾相连的顺序存储结构称为循环队列。当上面例子在加入一个元素时,rear移向0位置。


接着a6入队,将其放入到0处,rear的指针指向下标为1,



若再入队a7,则rear指针将会和front指针重合,通知指向下标为2的位置。


又有问题,之前说队空时是,front == rear, 现在队满也出现front == rear.

解决这个问题常用方法是:

队空时,front == rear,

队满时,修改其条件,保留一个元素位置。也就是说,队满时,还有一个空闲单元。

由于是循环队列,有可能rear比front大,也有可能比front小,所以尽管两者相差一个位置是满的情况,但也可能是相差了整整一圈,

所以更改队满条件(rear+1)%QueueSize == front 


通用的计算队列长度的公式:

(rear - front + QueueSize) %QueueSize


初始时: Q.front = Q.rear = 0

队首指针进1: Q.front = (Q.front + 1) % MaxSize

队尾指针进1: Q.rear = (Q.rear + 1)%MaxSize

队列长度:(Q.rear - Q.front + MaxSize)%MaxSize

判断队满条件:(Q.rear + 1)%MaxSize == Q.Front



/**************************************************循环队列**********************************************/
#define MAXSIZE 20  //存储空间初始分配量
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#include <stdlib.h>
typedef int QElemType;   //QElemType 数据类型
typedef int Status;

// 循环队列顺序存储结构

typedef struct
{
    QElemType data[MAXSIZE];
    int front;  //头指针
    int rear;   //尾指针
}SqQueue;


// 循环队列初始化代码
Status InitQueue(SqQueue *Q)
{
    Q->front = 0;
    Q->rear = 0;
    return OK;
}

// 循环队列求队列长度
// 返回Q个元素个数,也就是返回当前队列长度
int QueueLength(SqQueue Q)
{
    return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}


// 入队操作
// 若队列未满,则插入元素e到Q新的队尾

Status EnQueue(SqQueue *Q, QElemType e)
{
    if((Q->rear+1)%MAXSIZE == Q->front) //判断队满
        return ERROR;

    Q->data[Q->rear] = e;               //将元素e赋值到队尾
    Q->rear = (Q->rear+1)%MAXSIZE;      //rear指针向后移动一个位置,
                                        //若到最后通过(Q->rear + 1)%MAXSIZE移动到头部
    return OK;
}

// 出队操作
// 若队列不空,则删除Q中队头的元素,并返回其值
Status DeQueue(SqQueue *Q, QElemType *e)
{
    if(Q->front == Q->rear)             //判断队空
        return ERROR;

    *e = Q->data[Q->front];             //将队头元素赋值给e
    Q->front = (Q->front + 1)%MAXSIZE;  //front指针向后移动一个位置,若到最后则返回头部

    return OK;
}

队列链式存储结构及其实现

队列的链式存储结构,其实就是线性表的单链表,只不过是它只能尾进头出而已,把这种简称为链队列。
需要有一个头结点,当空队列时,front和rear都指向头结点

typedef int QElemType;  

typedef struct      //结点结构
{
    QElemType data;
    struct QNode *next;
}QNode,*QueuePtr;

typedef struct      //队列链表结构
{
    QueuePtr front,rear;    //队列头尾指针
}LinkQueue;


// 入队操作
// 插入元素e到Q的新队尾元素,直接插入,不管大小
Status EnQueue(LinkQueue *Q, QElemType e)
{
    QueuePtr s = (QueuePtr)malloc(sizeof(QNode));

    if(!s)                  // 存储分配失败
        exit(OVERFLOW);

    s->data = e;
    s->next = NULL;

    Q->rear->next = s;      //把拥有元素e的新结点放到原队列结点的后继

    Q->rear = s;            //把当前结点s设为队尾结点
    return OK;
}

// 出队操作
// 头结点的后继结点出队,将头结点的后继改为他后面结点,删除该结点
// 如果链表除头结点外只剩一个元素时,则需要将rear指向头结点

Status DeQueue(LinkQueue *Q, QElemType *e)
{
    QueuePtr p;

    if(Q->front == Q->rear)
        return ERROR;

    p = Q->front->next;     //将要删除结点暂存
    *e = p->data;           //将要删除结点值赋值给e
    Q->front->next = p->next;   // 将原队头结点后继指向p->next,

    if(Q->rear == p)            //若队头是队尾,则删除后将rear指向头结点
        Q->rear = Q->front;

    free(p);

    return OK;
}


循环队列与链队列,其从时间上,其实他们的基本操作都是常数时间O(1),

循环队列是要事先申请好空间,使用期间不释放;  链队列,每次申请和释放结点会存在一些时间开销,如果入队出队频繁,两者差异细微。


空间上,

循环队列必须有一个固定的长度,所以有了存储元素个数和空间的浪费。

链队列则不存在这个问题,尽管他需要一个指针域,会产生一些时间上的开销,但是可以接受。在空间上,链队列更加灵活。


SUMMARY

栈(stack) 是限定仅仅在表尾插入和删除操作的线性表。

队列(queue)是只允许在一端进行插入操作,而在另外一端进行删除操作的线性表。


这两种线性表均可以用顺序存储结构实现,但是会有顺序存储的弊端。

对于栈,如果是两个相同的数据类型栈,则可以用数组两端作为栈底的方法,让两个栈进行共享数据,这样可以最大化利用数组空间。


对于队列,为了避免数组插入和删除需要移动数据,于是引入了循环队列,使得队头和队尾可以在数组中循环变化,解决了移动数据时间的损耗,使得原本O(n)的插入和删除动作编程了O(1);


这两种数据结构也可以通过链式的存储结构来实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值