目录
一、概述
队列跟栈非常相似,也是一种操作受限的线性表数据结构。跟栈一样,队列可以用数组来实现,也可以用链表来实现,用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列。
队列最基本的操作也是两个:入队 enqueue()(放一个数据到队列尾部)和出队 delqueue()(从队列头部取一个元素),所以队列需要两个指针:一个是 head 指针,指向队头,一个是 tail 指针,指向队尾。
二、顺序队列
顺序队列的底层实现结构是数组,实现思想是:预分配一段空间,当入队时,若空间满,则丢弃或者阻塞,若空间不满,再判断tail指针是否在数组尾部,如果不在尾部,则直接入队即可,若tail指针已经在尾部,则将head至tail之间的数据迁移到数组起始位置,然后再入队。
此时队满的判断条件是 tail == n,队空的判断条件是 head == tail。
此时的时间复杂度如何分析呢?
如果队尾没有满,可以直接入队,时间复杂度为O(1)。
当tail=n的时候时间复杂度才迅速飙升为O(n),即需要进行n次搬移,此时n次的搬移如果均摊到0~n这n次上,其实总体的均摊复杂度还是O(1)。
三、链式队列
链式队列同样需要两个指针:head 指针和 tail 指针。它们分别指向链表的第一个结点和最后一个结点。如图所示,入队时,tail->next= new_node, tail = tail->next;出队时,head = head->next。
因为链式队列没有数据迁移的操作,所以它的入队和出队的时间复杂度都是O(1)。
三、循环队列
上面我们讲到顺序队列时候,当tail指针到达数据尾部时,此时如果再入队,将不得不进行数据迁移,实际上,我们可以通过代码来规避这个操作,那就是将顺序队列改造成循环队列。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,改成了一个环。
为了更好的理解,我们模拟一下这个循环队列入队的情况:下左图中这个队列的大小为 8,当前 head=4,tail=7。当有一个新的元素 a 入队时,我们放入下标为 7 的位置。但这个时候,我们并不把 tail 更新为 8,而是将其在环中后移一位,到下标为 0 的位置。当再有一个元素 b 入队时,我们将 b 放入下标为 0 的位置,然后 tail 加 1 更新为 1。所以,在 a,b 依次入队之后,循环队列中的元素就变成了下右图的样子:
那我们又如何判断循环队列的队空和队满呢?
队空时,和顺序队列一样,tail == head。
队满时呢?如下图,当队列处于如下状态时,再入队,此时tail将移动到head位置,即tail == head,但很明显,这和我们判断队空的条件一模一样,这样我们就无法区分出当tail == head时,队列处于什么状态,所以,为了区分开,我们将队列最接近满的时候的条件认定为队列满,即head == tail+1,也可以使用(tail+1)%n=head作为判断。
从下图可以看出,此时tail里面是没有存储数据的,即循环队列会浪费一个数组的存储空间,但这也是没办法。
四、示例代码
// Queue.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <new>
//QueueList 队列
//定义
template<class T>
class QueueList
{
public :
QueueList(int MaxListSize=10); //构造函数
~QueueList() //析构函数
{
delete[] elements;
}
bool IsEmpty() const //判断是否为空
{
return rear==front;
}
int Length() const //获取队列长度
{
return rear-front;
}
int EnQueue(T& x); //入队
T DeQueue(); //出队
T geteQueueData(); //获取队首数据,不出队
bool IsFull() const //判断是否满
{
return (rear+1)%MaxSize==front;
}
void clear() ;
private:
int rear; //队尾指针
int front; //队首指针
int MaxSize; //队列最大长度
T *elements;//一维动态数组
};
//实现...
template<class T>
QueueList<T>::QueueList(int MaxListSize)
{
//基于公式的线性表的构造函数
MaxSize=MaxListSize;
elements=new T[MaxSize];
rear = front = 0;
}
template<class T>
int QueueList<T>::EnQueue(T& x)
{
if(IsFull())
{
return -1;
}
elements[rear] = x;
rear = (rear+1)%MaxSize;
return 0;
}
template<class T>
T QueueList<T>::DeQueue()
{
T data;
if(IsEmpty())
{
return NULL;
}
data = elements[front];
front = (front+1)%MaxSize;
return data;
}
template<class T>
T QueueList<T>::geteQueueData()
{
if(IsEmpty())
{
return NULL;
}
return elements[front];
}
template<class T>
void QueueList<T>::clear()
{
delete []elements;
return ;
}
void QueueListSample()
{
int j = 0;
int a = 10,b = 20,c = 30,d = 40,e = 50;
QueueList<int> Q(6);
std::cout<<"Length="<<Q.Length()<<std::endl;
std::cout<<"IsEmpty="<<Q.IsEmpty()<<std::endl;
Q.EnQueue(a);
Q.EnQueue(b);
Q.EnQueue(c);
Q.EnQueue(d);
Q.EnQueue(e);
j = Q.DeQueue();
std::cout<<j<<std::endl;
j = Q.DeQueue();
std::cout<<j<<std::endl;
j = Q.DeQueue();
std::cout<<j<<std::endl;
j = Q.DeQueue();
std::cout<<j<<std::endl;
j = Q.DeQueue();
std::cout<<j<<std::endl;
return;
}
int _tmain(int argc, _TCHAR* argv[])
{
QueueListSample();
//暂停操作
char str;
std::cin>>str;
//程序结束
return 0;
}
运行结果为: