以下内容主要参考了严蔚敏版的数据结构教材
和栈相反,队列是一种先进先出的数据结构,它只允许在队列的一端进行插入操作,也就是入队列,在另一端进行删除操作,也就是出队列。允许插入的一端叫做队列尾,允许删除的一端叫做队列头。这种数据结构就和我们平时日常生活中排队进行某种活动相似,先来的排在队伍前面的人可以先进行活动,后来的人只能加在队伍的尾部排队。队列的简单示意图如图1所示。队列也是一种线性表。
![](https://i-blog.csdnimg.cn/blog_migrate/fc058a1f981b4f7cbca62a9fd75b6414.png)
还有一种特殊的队列:双端队列,这种队列在队列的两端都可以进行插入和删除操作,假设我们称队列的两端为端A和端B,这里在端A插入的元素可以在端A删除也可以在端B删除,同样在端B插入的元素可以在端A删除也可以在端B删除。如果假定在端A插入的元素只能在端A删除,在端B插入的元素只能在端B删除,这时双端队列就变成栈底相邻接的两个栈了。还有受限的双端队列:
- 输入受限的双端队列:一个端点可以进行插入和删除,但是另一个顶点只能进行删除。这里没有在哪个端点插入就必须在哪个端点删除的限制。
- 输出受限的双端队列:一个端点可以进行插入和删除,但是另一个顶点只能进行插入
和线性表一样,队列也有两种存储表示,链式表示,用链表存储队列,和顺序表示,用一片连续的内存空间存储队列。我们先来看队列的链式表示。队列的链式表示的简单示意图如图2所示。这里特别要注意的地方是表示队列的链表的第一个节点是不存储任何队列元素的,第一个节点叫做头节点,从第二个链表节点才开始存储队列元素,最后一个节点叫做尾结点。队列的链式表示中有两个指针 f r o n t front front和 r e a r rear rear,它们分别指向队列的链式表示的头节点和存储队列的尾部的最后一个元素的节点,也就是尾结点,这两个指针的存在方便了队列元素的插入和删除。队列的链式表示的插入和删除这里就是线性表的链式表示的插入和删除了,这里就不特别介绍了,只是插入的位置在头结点之后,删除的位置永远是尾结点。队列空的时候两个指针 f r o n t front front和 r e a r rear rear都指向头节点。
![](https://i-blog.csdnimg.cn/blog_migrate/fa45e493e39cbc987c79e597e44269a2.png)
下面是队列的链式实现的简单代码,图3是测试结果。代码参考于这里
class QueueNode
{
private:
int data;
QueueNode* next;
public:
QueueNode() :data(0), next(0) {};
QueueNode(int x) :data(x), next(0) {};
int get_data()
{
return data;
}
QueueNode* get_next()
{
return next;
}
void set_data(int current_data)
{
data= current_data;
}
void set_next(QueueNode* next_value)
{
next= next_value;
}
};
class QueueList
{
private:
QueueNode* front;
QueueNode* rear;
int size;
public:
QueueList();
void Push(int x);
void Pop();
bool IsEmpty();
int getFront();
int getBack();
int getSize();
};
QueueList::QueueList()
{
front= new QueueNode();
rear = front;
size = 0;
}
void QueueList::Push(int x)
{
QueueNode* newNode = new QueueNode(x);
rear->set_next(newNode);
rear = newNode;
size++;
}
void QueueList::Pop()
{
if (IsEmpty())
{
cout << "Queue is empty"<<endl;
return;
}
QueueNode* deleteNode = front->get_next();
front->set_next(deleteNode->get_next());
if (deleteNode == rear)
{
rear = front;
}
delete deleteNode;
size--;
}
int QueueList::getFront()
{
if (IsEmpty())
{
std::cout << "Queue is empty.\n";
return -1;
}
return front->get_next()->get_data();
}
int QueueList::getBack()
{
if (IsEmpty())
{
std::cout << "Queue is empty.\n";
return -1;
}
return rear->get_data();
}
bool QueueList::IsEmpty()
{
return (front==rear);
}
int QueueList::getSize()
{
return size;
}
int main()
{
QueueList q;
if (q.IsEmpty())
{
std::cout << "Queue is empty."<<endl;
}
q.Push(24);
std::cout << "After push 24:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Push(8);
std::cout << "After push 8:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Push(23);
std::cout << "After push 23:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Push(13);
std::cout << "After push 13:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Push(35);
std::cout << "After push 35:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
q.Pop();
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/400b4a10258440f43efe431656287903.png)
下面我们来看队列的顺序实现。队列的顺序实现可以简单的理解为预先分配一定数量的以队列的元素为单位的类似于数组的内存空间,以后队列的插入,删除和存储操作都在这个分配的空间中进行。队头的方向为向着索引变小的方向,队尾的方向为向着索引变大的方向,如图4所示。和队列的链式表示类似,这里也有两个索引 f r o n t front front和 r e a r rear rear用来标示队列的头部和尾部,所不同的是这里需要特别注意的是,队列的顺序表示中 f r o n t front front永远指向着队列的队头, r e a r rear rear永远指向着队尾元素的下一个元素。 f r o n t = r e a r front=rear front=rear表示队列空。
![](https://i-blog.csdnimg.cn/blog_migrate/949d2792aa9de79fce8ad5d2f036119a.png)
这里有一个问题是,在图5中这时分配给队列的整个存储空间实际上只存储了一个队列元素,但是此时按照队列的规则已经不能再在队尾插入元素了(如果允许队列满的情况下增加存储空间的话,此时只能增加存储空间之后才能再次插入元素,图5的情况对于队列的顺序表示且分配固定内存空间的情况下实际上是没有满的,满的情况对应于图6,假设留出一个队列元素的空间用来表示队列满)。为了避免图5的这种浪费内存空间的情况,在一次性分配一定内存空间且之后不能再增加内存空间的情况下,我们将分配的内存空间看做是一个环形结构,如图7所示。在这种结构下 f r o n t front front还是永远指向着队列的队头, r e a r rear rear还是永远指向着队尾元素的下一个元素。 f r o n t = r e a r front=rear front=rear表示队列空。这里为了表示队列满,分配的固定的空间的一个元素空间是不使用的,队列满的条件是 f r o n t = r e a r + 1 front=rear+1 front=rear+1,不然的话队列空和满的情况下都是 f r o n t = r e a r front=rear front=rear,如图8和图9所示。实际队列满的情况是图10。这里需要注意的是因为这里我们将分配的固定空间想象为一个环形的结构,因此当队列的元素索引为最大值时,这这个索引再加一就又是0了,这里就是一个取模运算。对于图7中的有12个元素空间的队列,当元素索引为11时再加一就是0了。因此为了避免内存空间的浪费,如果应用程序中可以预估最大使用的队列的空间就可以使用队列的顺序表示的环形结构,否则就必须使用链式结构。
![](https://i-blog.csdnimg.cn/blog_migrate/e901967f5463278a938762c20e8c74be.png)
![](https://i-blog.csdnimg.cn/blog_migrate/26ff25fc37eb78ddd42d682063c8cb28.png)
![](https://i-blog.csdnimg.cn/blog_migrate/61d710e3030f4626cb943dd583266479.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c6b005852604bc9799e05e8b48b07d49.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c51c9b6764dabd6f9b4097d0f3caedbf.png)
![](https://i-blog.csdnimg.cn/blog_migrate/fe4e60677d13c3494c30692bbf3094ec.png)
下面我们简单看一下队列的顺序实现的代码,测试结果和图3一样。
#include"Header.h"
#define MAX_QUEUE_SIZE 7
class QueueNode
{
private:
int data;
public:
QueueNode() :data(0) {};
QueueNode(int x) :data(x) {};
int get_data()
{
return data;
}
void set_data(int current_data)
{
data= current_data;
}
};
class QueueList
{
private:
int front;
int rear;
QueueNode* base;
public:
QueueList();
void Push(int x);
void Pop();
bool IsEmpty();
bool IsFull();
int getFront();
int getBack();
int getSize();
};
QueueList::QueueList()
{
front= 0;
rear = 0;
base=new QueueNode[MAX_QUEUE_SIZE];
}
void QueueList::Push(int x)
{
if (IsFull())
{
cout << "Queue is full when push an element" << endl;
return;
}
base[rear].set_data(x);
rear= (rear + 1) % MAX_QUEUE_SIZE;
}
void QueueList::Pop()
{
if (IsEmpty())
{
cout << "Queue is empty when pop an element"<<endl;
return;
}
front = (front + 1) % MAX_QUEUE_SIZE;
}
int QueueList::getFront()
{
if (IsEmpty())
{
std::cout << "Queue is empty when get queue head element."<<endl;
return -1;
}
return base[front].get_data();
}
int QueueList::getBack()
{
if (IsEmpty())
{
std::cout << "Queue is empty when get queue tail element." << endl;
return -1;
}
return base[(rear-1)%MAX_QUEUE_SIZE].get_data();
}
bool QueueList::IsEmpty()
{
return (front==rear);
}
bool QueueList::IsFull()
{
return (front == (rear+1) % MAX_QUEUE_SIZE);
}
int QueueList::getSize()
{
return (rear-front+MAX_QUEUE_SIZE);
}
int main()
{
QueueList q;
if (q.IsEmpty())
{
std::cout << "Queue is empty."<<endl;
}
q.Push(24);
std::cout << "After push 24:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Push(8);
std::cout << "After push 8:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Push(23);
std::cout << "After push 23:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Push(13);
std::cout << "After push 13:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Push(35);
std::cout << "After push 35:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
std::cout << "front: " << q.getFront() << " back: " << q.getBack() << endl;
q.Pop();
std::cout << "After pop the front element:" << endl;
q.Pop();
return 0;
}
下面我们简单看