持续创作,加速成长!这是我参与「掘金日新计划 · 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.
- 初始状态的队列
- 入队操作:入队9个元素,此时队尾指针
rear应加1,指向新的队尾元素位置后,将新元素value的值赋值进去,因此入队操作为rear++; array[rear] = value;,如下所示。
- 出队操作:出队三个元素,此时队头指针
front应加1,表示队头元素出队。因此出队的操作为front++; value = array[front];
在上述操作中,如果再入队2个元素就会发生溢出。但事实上,此时的队列并未真的队满。队列前面还有三个位置可以利用起来。像这种情况就称为“假溢出”现象。
- 总结:
当队尾指针移动到最后时,在进行入队操作就会出现溢出,但有时队列中并未出现真正的满员。产生这种“假溢出”现象的原因是队列本身的操作限制,即“队头出队,队尾入队”
(3) 循环队列的介绍
前面讲了,顺序队列有一种“假溢出”现象。
此时我们就想,能不能当队尾满了,就从头开始入队(如果队头有可用的位置),也就是说,队列的头尾相接,构成循环。
我们将这种头尾相接的队列称为循环队列。
设循环队列的容量为maxSize,则满足队空和队满的条件都是:rear == front.
为解决队满和队空的发生条件相同,这里提出两种解决方法:
- 设置一个变量count,用于存储队列中数据元素的个数。
当发生入队操作时,count就自增加一,发生出队操作时,count则自减。
此时:
- 队空条件为:
count == 0 - 队满条件为:
count == maxSize
- 牺牲一个单元存储空间,用于区分队空和队满。
在这个方法下,规定队头指针front指向的单元不能存储队列元素,只起到标志作用,而队尾指针rear则直接指向队尾元素。
此时:
- 队空条件为:
rear == front - 队满条件为:
(rear + 1) % maxSize == front
本文记录的是方法二。
在方法二下,记录若干表达式如下:
- 入队操作:
rear = (rear + 1) % maxSize - 出队操作:
front = (front + 1) % maxSize - 求队列中元素的个数:
(rear - front + maxSize) % maxSize
(4) 循环队列的类定义
循环队列的抽象数据类型定义如下:
(5) 构造函数
初始化一个循环队列,如下所示:
(6) 入队
对于入队操作,需要先移动队尾指针,再将插入的元素存入队尾指针指向的存储单元中。
如下所示:
(7)出队
对于出队操作,需要移动队首指针,并返回队首元素。
代码如下:
(8) 扩容
扩大队列空间:
2.3、循环队列的解释
对于循环队列,这里举个例子。
下面是一个容量为5(有一个单元空间作为占用)的循环队列。
在上述循环队列中,每入队一个元素,操作有两步:
rear = (rear+1) % maxSize;data[rear] = val;
此时入队五个元素,rear的值就为5,front的值不变,示意图如下:
接着,进行出队操作,同样步骤如下:
front = (front+1) % maxSize;return data[front]
这时出队两个数据元素,示意图如下:
在上述结果中,如果是顺序队列,此时再入队一个元素就溢出了。
示意图如下:
但是对于循环队列:
- 此时
front=2,rear=5 - 对于入队操作:
rear = (rear+1) % maxSize; - 也就是
rear = (5+1) % 6,即rear = 0 - 这时就可以成功入队,而不会出现“假溢出”现象;
示意图如下;
3、写在最后
好了,文章的内容就到这里,感谢观看。