【算法与数据结构】:循环队列的C++实现以及举例解释

347 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情

1、写在前面

大家好,我是翼同学。今天文章的内容是:

  • 队列

2、内容

2.1、概念

(1) 什么是队列?

队列:一种只允许在表的一端插入,在另一端删除的线性数据结构。

在队列中:

  • 允许插入数据元素的一端称为队尾,一般记作rear;
  • 允许删除数据元素的一端称为队头,一般记作front;
  • 当队列中没有数据元素时,称为空队列。

(2) 队列的特点

队列是一种操作受限的线性表,其特点是先进先出(First In First Out)。

这就好比,我们去排队做核酸,先来的人排在前面,后来的人排在队尾,前面的人做完核酸就离开了,后面的人就跟上,即先到先得。

(3) 队列的抽象数据类型定义

template<class T>
class Queue {
public:

    // 判断队列是否为空
    virtual bool empty()const = 0;

    // 清空队列
    virtual void clear() = 0;

    // 求队列长度
    virtual int size() const = 0;

    // 入队
    virtual void enQueue(const T& val) = 0;

    // 出队
    virtual T deQueue() = 0;

    // 取队头元素
    virtual T getHead() const = 0;

    // 虚析构函数
    virtual ~Queue() {}
};

2.2、循环队列的C++实现

(1) 顺序队列

队列可以利用顺序存储结构来表示和实现,一般使用数组来实现。为了方便运算,我们建立了两个指针,其中规定队头指针front指向队头元素的前一个位置,队尾指针rear指向队尾元素,并且可以注意到,所有的插入操作都在队列的队尾进行,所有的删除操作都在队头操作。

备注:我们也可以规定队头指针front指向队头元素,队尾指针rear指向队尾元素的后一个位置。

(2) 顺序队列的缺点

事实上,顺序队列存在一个缺点。就是当队满的时候,会出现“假溢出”的现象。

什么是“假溢出”?

举个例子。

由于队头指针front指向队头元素的前一个位置,队尾指针rear指向队尾元素。因此假设现在有一个容量为10的顺序队列,其初始状态下的队头指针front的值应等于-1,队尾指针rear的值也为-1.

  • 初始状态的队列

image.png

  • 入队操作:入队9个元素,此时队尾指针rear应加1,指向新的队尾元素位置后,将新元素value的值赋值进去,因此入队操作为rear++; array[rear] = value;,如下所示。

image.png

  • 出队操作:出队三个元素,此时队头指针front应加1,表示队头元素出队。因此出队的操作为front++; value = array[front];

image.png

在上述操作中,如果再入队2个元素就会发生溢出。但事实上,此时的队列并未真的队满。队列前面还有三个位置可以利用起来。像这种情况就称为“假溢出”现象。

  • 总结:

当队尾指针移动到最后时,在进行入队操作就会出现溢出,但有时队列中并未出现真正的满员。产生这种“假溢出”现象的原因是队列本身的操作限制,即“队头出队,队尾入队”

(3) 循环队列的介绍

前面讲了,顺序队列有一种“假溢出”现象。

此时我们就想,能不能当队尾满了,就从头开始入队(如果队头有可用的位置),也就是说,队列的头尾相接,构成循环。

我们将这种头尾相接的队列称为循环队列。

设循环队列的容量为maxSize,则满足队空和队满的条件都是:rear == front.

为解决队满和队空的发生条件相同,这里提出两种解决方法:


  1. 设置一个变量count,用于存储队列中数据元素的个数。

当发生入队操作时,count就自增加一,发生出队操作时,count则自减。

此时:

  • 队空条件为:count == 0
  • 队满条件为:count == maxSize

  1. 牺牲一个单元存储空间,用于区分队空和队满。

在这个方法下,规定队头指针front指向的单元不能存储队列元素,只起到标志作用,而队尾指针rear则直接指向队尾元素。

此时:

  • 队空条件为:rear == front
  • 队满条件为:(rear + 1) % maxSize == front

本文记录的是方法二。

在方法二下,记录若干表达式如下:

  • 入队操作:rear = (rear + 1) % maxSize
  • 出队操作:front = (front + 1) % maxSize
  • 求队列中元素的个数:(rear - front + maxSize) % maxSize

(4) 循环队列的类定义

循环队列的抽象数据类型定义如下:

carbon (18).png

(5) 构造函数

初始化一个循环队列,如下所示:

image.png

(6) 入队

对于入队操作,需要先移动队尾指针,再将插入的元素存入队尾指针指向的存储单元中。

如下所示:

image.png

(7)出队

对于出队操作,需要移动队首指针,并返回队首元素。

代码如下:

image.png

(8) 扩容

扩大队列空间:

image.png

2.3、循环队列的解释

对于循环队列,这里举个例子。

下面是一个容量为5(有一个单元空间作为占用)的循环队列。

image.png


在上述循环队列中,每入队一个元素,操作有两步:

  1. rear = (rear+1) % maxSize;
  2. data[rear] = val;

此时入队五个元素,rear的值就为5,front的值不变,示意图如下:

image.png


接着,进行出队操作,同样步骤如下:

  1. front = (front+1) % maxSize;
  2. return data[front]

这时出队两个数据元素,示意图如下:

image.png


在上述结果中,如果是顺序队列,此时再入队一个元素就溢出了。

示意图如下:

image.png


但是对于循环队列:

  • 此时front=2rear=5
  • 对于入队操作:rear = (rear+1) % maxSize;
  • 也就是rear = (5+1) % 6,即rear = 0
  • 这时就可以成功入队,而不会出现“假溢出”现象;

示意图如下;

image.png


3、写在最后

好了,文章的内容就到这里,感谢观看。