1.队列的概念
队列只允许在表的一端插入,另一端删除。允许插入的一端叫做队尾,允许删除的一端叫做对首。队列的特性叫“先进先出”。和栈一样,队列的存储形式也有两种,基于数组的存储表示和基于链表的存储表示。本文先实现基于数组的存储队列,也叫顺序队列。在顺序队列中设置两个指针,front和rear,front指示队头的位置,rear指示队尾的位置(说是指针,实际仍不是c语言的指针*,而是类似下标或索引的作用)。如下图所示:
- 当队列为空时,front=rear=0;
- 有新元素入队时,先将新元素添加到rear所指的位置,然后再讲rear加1;因而rear指示了实际队尾位置的下一个位置,即下一个元素应当加入的位置。如上图的图b所示。
- 当队首元素出队时,先将队首元素记录下来,然后front指针加1,指示新的队头的位置,然后将记录的元素值返回,如上图c所示。
2.顺序队列存在的问题
如果上图图c中B再出队,则front会在C的位置,这样原来的索引为0和1的内存就会是空的,这时数组的前端还有空位置,这就造成了资源的浪费。所以,为了能充分的利用数组中的存储空间,把数组的前端和后端连接起来,形成一个闭环,这样的队列叫做循环队列,如下图所示。
上图循环队列最多存maxSize=8个元素,这样如果一直往队列中加入新元素,则当rear=maxSize-1=7时,如果再进一个元素,则rear就会变为0;同理,若此时一直往外出队数据,则当H出队后front也会变为0。这样的操作可以利用取余%来实现:
队头指针进1:front = ( front + 1) %maxSize;
队尾指针进1:rear= (rear + 1)%maxSize;
3.循环队列队满和队空的判断条件
如果循环队列出队速度快于入队速度,则front会追上rear,此时front == rear,队列为空;
如果入队速度快于出队速度,则rear会追上front,但是为了区别于上面的队空条件(front ===rear),我们用(rear + 1)%maxSize == front来判断是否队满。也就是说,当rear指到front的前一个位置时,我们就认为队列满了,如上图总的最后一个小图所示,此时rear指向的位置不能再存元素了,如果不留这个位置,再入队最后一个新元素,此时rear == front,这时队列实际是满的,可代码会认为这是队空的条件,进而判断此时队列为空,因此产生混淆。所以,当rear指到front的前一个位置时,我们就认为队列满了,即(rear + 1)%maxSize == front,队满,队列不再能入队新元素。
这样构成的循环队列最多能存放maxSize - 1个元素。
4.队列基类的头文件"Queue.h"
这个头文件定义了队列通用的一些方法,循环队列,包括后面的链式队列均可以继承实现多态。代码如下:
#pragma once
const int maxSize = 50; //顺序队列的最大元素个数
template <class T>
class Queue
{
public:
Queue() {};
~Queue() {};
virtual bool EnQueue(const T& x) = 0; //新元素入队
virtual bool DeQueue(T& x) = 0; //队头元素出队
virtual bool getFront(T& x) = 0; //获取队头元素
virtual bool IsEmpty() const = 0; //判断队列是否为空
virtual bool IsFull() const = 0; //判断队列是否满。顺序队列需要,链式队列不需要,但仍要重写
virtual int getSize() const = 0; //求队列元素的个数
};
//用struct定义节点,链式队才用的到
template <class T>
struct LinkNode {
T data; //数据域
LinkNode<T> * link; //指针域,指向下一个节点
LinkNode() //仅初始化指针的构造函数
{
LinkNode<T> *ptr = nullptr;
link = ptr;
}
LinkNode(const T& item, LinkNode<T> *ptr = nullptr) //初始化数据和指针成员的构造函数
{
data = item;
link = ptr;
}
};
5.循环队列头文件“SeqQueue.h”
该头文件定义循环队列的类以及其属性即接口实现,重写了队列基类Queue的函数,实现多态。代码如下;
#pragma once
//顺序队列,存储形式是数组。为了有效利用内存,设计成循环队列
# include <iostream>
# include <assert.h>
# include "Queue.h"
using namespace std;
template <class T>
class SeqQueue :public Queue<T> //继承时注意Queue也是类模板,后面加<T>区别普通的类
{
public: //如果不加public,权限为private
SeqQueue(int sz=10); //构造函数
~SeqQueue() { delete[] elements; } //[]紧跟delete,表明释放的elements地址是一个数组,而不是单个变量
bool IsFull() const{ return ((rear + 1) % maxSize == 0) ? true : false; }//是否队满
bool IsEmpty() const { return (rear == front) ? true : false; } //是否队空
bool EnQueue(const T& x); //新元素入队
bool DeQueue(T& x); //队头元素出队
bool getFront(T& x); //获取队头元素
void makeEmpty() { rear = front = 0; }//清空队列(其实队列内容未被修改,只是将指针置0)
int getSize() const ; //求队列元素的个数
template <class R>
friend ostream & operator<<(ostream & out, SeqQueue<R> &sq);//重载<<输出队列内容
private:
int rear, front; //队尾和队首的指针,同链式栈一样,不是真正的指针,而是下标。front是第一个元素的下标,rear是最后一个元素下一个位置的坐标
T * elements; //指向存放数据的数组的指针
int maxSize; //队伍最大可容纳元素的个数
};
//构造函数
template <class T>
SeqQueue<T>::SeqQueue(int sz):front(0),rear(0),maxSize(sz) //这里的sz不允许使用默认参数
{
//建立一个最大容量为maxSize的空队列
//rear = front = 0;
//maxSize = sz;
elements = new T[maxSize];
assert(elements != nullptr);//断言函数,括号里的内容成立,则继续执行代码;否则,终止代码的执行
}
//新元素入队尾
template <class T>
bool SeqQueue<T>::EnQueue(const T& x)
{
if (IsFull() == true)
return false; //队列满则插入失败
elements[rear] = x; //队尾位置插入新元素
rear = (rear+1)%maxSize; //队尾指针更新
return true;
}
//队头元素出队
template <class T>
bool SeqQueue<T>::DeQueue(T& x)
{
if (IsEmpty()) return false;
x = elements[front];
front = (front + 1) % maxSize; //队头指针加一
return true;
}
//获取队头元素
template <class T>
bool SeqQueue<T>::getFront(T &x)
{
if (IsEmpty()) return false;
x = elements[front];
return true;
}
//求队列元素的个数
template <class T>
int SeqQueue<T>::getSize() const
{
return (rear - front + maxSize) % maxSize;
//加maxSize是为了保证队列只入队且队列入满时rear=0,front=1,此时rear-front=-1,
//此时元素个数为maxSize-1,由此加上maxSize可以得到正确结果
}
template <class R>
ostream & operator<<(ostream & out, SeqQueue<R> &sq)
{
out << "front = " << sq.front << ",rear = " << sq.rear << endl;
for (int i = sq.front; i != sq.rear; i = (i + 1) % sq.maxSize)
{
out << i << " : " << sq.elements[i] << endl;
}
return out;
}
6.代码测试
放在“循环队列.cpp”文件中,老规矩只做简单测试,代码如下:
#include<iostream>
#include "SeqQueue.h"
using namespace std;
int main()
{
SeqQueue<int> sq(10);
for (int i = 0; i < 10; i++)
sq.EnQueue(i);
cout << sq << endl;
}
结果如下图所示: