一.队列的定义
队列是只允许一端在进行插入操作,而另一端进行删除操作的线性表
First In First Out的线性表,简称FIFO,允许插入的一段称为队尾,允许删除的一端称为队头
二.循环队列
1.队列顺序存储的不足
假设一个队列有n个元素,则顺序存储的队列需要建立一个大于n的数组
入队列操作,就是在队尾追加一个元素,因此时间复杂度为O(1)
与栈不同的是,队列的元素是在队头出列,队列中的所有元素都得向前移动,所以时间复杂度为O(n)
我们引入两个指针,front指向队头元素,rear指向队尾元素的下一个位置
出队时,front向后移动
入队时,rear向后移动
当front==rear时,表示队空
但是数组元素个数正好等于数组内存空间时,会出现rear移动到数组之外的错误,而front之前移动过的地方还是空闲的
为了解决这种假溢出的问题,我们使用循环队列
2.循环队列的定义
为了区别队空和队满的条件,我们一般会保留一个元素空间
也就是说,队满的时候,数组中还有一个空闲单元
毕竟是循环链表,循环过程中rear可能会加到大于数组大小maxSize的值
所以我们的队满条件改为如下:
(rear+1)%maxSize==front
在计算队列长度的时候,我们也要考虑到各种情况
当rear>front的时候,len=rear-front
当rear<front的时候,len=rear-front+maxSize
所以通用的计算队列长度公式为:
(rear-front+maxSize)%maxSize
有了这些知识储备,我们开始写循环队列的顺序储存类代码:
template <class T>
class SeqQueue{
private:
int front,rear;
int maxSize;
T *element;
public:
SeqQueue(int sz=20); //构造函数
~SeqQueue(){delete []elements;}
bool EnQueue(T x);//若队列不满,则将x进队,否则队溢出处理
bool DeQueue(T& x);
//若队列不空,则退出队头元素x并由函数返回true;否则队列空,返回false
bool getFront(T& x);
//若队列不为空, 则返回true及队头元素的值, 否则返回false
bool IsEmpty() const { return (front==rear); }
bool IsFull() const
{ return ((rear+1)%maxSize == front); }
int getSize()const //求队长的函数
{ return (rear-front+maxSize)%maxSize; }
void makeEmpty() { front=rear=0; }
void output();
}
入队操作算法思路:
-
判断是否队满
-
赋值操作,然后将rear的指针向后移动一位
-
一定要记得%maxSize
template <class T>
bool EnQueue(T x){
if((rear+1)%maxSize == front){
return false;
}
elements[rear]=x;
rear=(rear+1)%maxSize;
return true;
}
出队操作算法思路:
-
判断是否队空
-
将删除元素赋值带出,front指针向后移动一位
-
一定要记得%maxSize
template <class T>
bool DeQueue(T &x){
if(front==rear){
return false;
}
x=elemens[front];
front=(front+1)%maxSize;
return true;
}
三.队列的链式存储形式
队列的链式存储结构,其实就是单链表,只不过只能尾进头出
直接上代码
template <class T>
struct LinkNode //链表结点类的定义
{
T data; //数据域
LinkNode<T>* link; //指针域
LinkNode() { link = NULL; } //构造函数1
LinkNode(const T& item, LinkNode<T>* ptr = NULL)
{
data = item; link = ptr;
} //构造函数2
};
template <class T>
class ListQueue{
private:
LinkNode<T>* front,rear; //头指针
}
出队操作
template <class T>
bool EnQueue(T x){
LinkNode<T>* s;
s.data=x; //给新节点赋值
s.next=NULL;
rear.next=s; //将新结点插入,相当于把rear指针移到最后
rear=s;
return true;
}
入队操作
template <class T>
bool DeQueue(T &x){
LinkNode<T>* p;
if(front==rear){ //判断是否队空
return false;
}
p=front.next; //把p指向front之后
x=p.data; //赋值带出
front.next=p.next; //
if(rear==p){
rear=front;
}
delete p;
return true;
}
总的来说,确定队列长度最大值的情况下,建议使用循环队列
如果无法预估队列长度的时候,则使用链队列