队列

一、队列初探

队列(queue)是只允许在一端进行插入操作,在另一端进行删除操作。其中,插入元素(即入队列)的一端为队尾(rear),删除元素的一端为队头(front)。与栈不同的是,队列是一种先进先出的线性表,简称FIFO。

假设队列q = (a1, a2, …, an),那么a1为队头,an为队尾。如下图所示:


二、顺序队列的不足

假设我们有队列q = (a1, a2, …, an),顺序队列需要建立一个大于n的数组,以存放n个元素。数组下标依然从零开始,下标为0的位置存放队头。插入元素即在队尾追加一个元素,不需要移动任何元素,时间复杂度为O(1),如下图所示:

这里写图片描述

然而,出队列时队头以后的元素都要往前移动一个位置,以保证队列的队头(下标为0的位置)不为空,时间复杂度为O(n),如下图所示:

这样的实现和线性表顺序存储结构完全相同。但是这种顺序队列性能低下,每次出队列都要耗费O(n)时间复杂度的代价。有没有解决方案呢?当然。前看下一小节是如何修正的。


三、循环队列弥补顺序队列的不足

假设我们的队列是长度为5的数组,那么初始状态为空队列,如下:

3.1 初始队列

显然,初始队列为空队列,而初始的队列满足:front == rear,均指向下标为0的位置。

3.2 入队列

现在为空队列依次插入4个元素a1, a2, a3, a4如下所示:

这里插入4各元素,一如顺序队列,rear需向后移动4个位置(rear + 4),front仍然指向下标为0的位置(即front == 0)。

那么,每有一个元素入队列,指向队尾的指针rear会向后移动1个位置:rear + 1

3.3 出队列

现在出队a1, a2,则front指针指向下标为2的位置,rear不变,如下图所示:

那么,每有1个元素出队列,指向队头的指针front向后移动1个位置:front + 1

3.3 为什么是循环队列

在上图情况下,当元素a5入队列,指向队尾的指针rear向后移动1个位置,但后面“?”的位置已经溢出数组,那么就让它向后移动一个位置,不过rear现在指向下标为0的位置,如下图所示,这就是循环队列。

3.4 何时空何时满

  • 队列为空的条件:front == rear
  • 队列满的条件(假设队列最大长度为MAXSIZE,即数组的长度):(rear + 1)%MAXSIZE == front

需要特别注意:当队列满时,数组中仍有一个空闲位置。但为什么要取模?请看下图:

那么,同样道理。

  • 每有插入一个元素,rear向后移动一个位置,如果像上图左图的情况,通用的入队列的条件:rear = (rear + 1)%MAXSIZE
  • 每删除一个元素,front向后移动一个位置,如果像上图左图的情况,通用的出队列的条件:front = (front + 1)%MAXSIZE

3.5 当前队列的长度

  • rear > front,即上图的左图,此时队列的长度为:rear - front
  • rear < front,即上图的右图,此时队列分为两段:MAXSIZE - front和0 + rear

因此,通用的计算队列长度的公式为:(rear - front + MAXSIZE)%MAXSIZE


四、队列相关操作的代码

#include <iostream>
using namespace std;

typedef int DataType;
const int MAXSIZE = 5;

struct Queue
{
    int front;
    int rear;
    DataType data[MAXSIZE];
};

// 初始化一个空队列
void InitQueue(Queue* queue)
{
    queue->front = 0;
    queue->rear = 0;
}

// 当前队列长度
int QueueLength(Queue queue)
{
    return (queue.rear - queue.front + MAXSIZE) % MAXSIZE;
}

// 入队列——插入元素
void EnQueue(Queue* queue, DataType elem)
{
    // 队列满
    if ((queue->rear + 1) % MAXSIZE == queue->front)
    {
        cout << "插入失败,队列已满!";
        return;
    }

    // 队尾插入元素,队尾位置下标后移
    queue->data[queue->rear] = elem;
    queue->rear = (queue->rear + 1) % MAXSIZE;
}

// 出队列——删除元素,返回被删除的元素
DataType DeQueue(Queue* queue, DataType* elem)
{
    //队列 空否
    if (queue->rear == queue->front)
    {
        cout << "队列为空,无法删除元素!" << endl;
        return 0;
    }

    *elem = queue->data[queue->front];
    queue->front = (queue->front + 1) % MAXSIZE;

    return *elem;
}


void main()
{
    Queue* queue = new Queue();

    InitQueue(queue);

    EnQueue(queue, 0);
    EnQueue(queue, 1);
    EnQueue(queue, 2);
    EnQueue(queue, 3);
    EnQueue(queue, 4);

    int len = QueueLength(*queue);

    cout << "当前队列元素的个数:" << len << endl;


    for (int i = 0; i < len; ++i)
    {
        cout << queue->data[i]<< " ";
    }

    delete queue;

    cin.get();
}

执行结果如下图所示:


五、总结

  • 当循环队列每有1个元素出队列或入队列时,指针front或rear都需要向后移动1个位置,但注意取模操作
  • 队列满的条件:(rear + 1)%MAXSIZE == front

参考:《大话数据结构》 程杰

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值