队列
队列的基本概念
1.队列的定义:
队列(Queue)简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列中插入元素称为入队或进队;删除元素称为出队或离队。这和我们日常生活中的排队是一致的,最早排队的也是最早离队的,其操作的特性是先进先出**(First In First Out, FIFO)**,如图所示
队头(Front):允许删除的一端,又称队首。
队尾(Rear):允许插入的一端。
空队列:不含任何元素的空表。
2.队列常见的基本操作:
需要注意的是,栈和队列是操作受限的线性表,因此不是任何对线性表的操作都可以作为栈和队列的操作。比如,不可以随便读取栈或队列中间的某个数据。
队列的顺序存储结构
1.队列的顺序存储:
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针front指向队头元素,队尾指针rear指向队尾元素的下一个位置(不同教材对front和rear的定义可能不同,例如,可以让rear 指向队尾元素、front指向队头元素。对于不同的定义,出队入队的操作是不同的)。
队列的顺序存储类型可描述为:
#define MaxSize 50 //定义队列中元素的最大个数
template<typename T>
struct SqQueue {
T data[MaxSize]; //存放队列元素
int front, rear; //队头指针和队尾指针
};
初始状态(队空条件):Q.front==Q.rear==O
。
进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。
出队操作:队不空时,先取队头元素值,再将队头指针加1。
图(a)所示为队列的初始状态,有Q.front==Q.rear==0
成立,该条件可以作为队列判空的条件。但能否用Q.rear==Maxsize作为队列满的条件呢?显然不能,图(d)中,队列中仅有一个元素,但仍满足该条件。这时入队出现“上溢出”,但这种溢出并不是真正的溢出,在data数组中依然存在可以存放元素的空位置,所以是一种“假溢出”。
2.循环队列:
前面已指出了顺序队列的缺点,这里引出循环队列的概念。将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。当队首指针Q.front=MaxSize-1
后,再前进一个位置就自动到0,这可以利用除法取余运算(%)来实现。
- 初始时:
Q.front=Q.rear=0
。 - 队首指针进1:
Q.front=(Q.front+1)%Maxsize
。 - 队尾指针进1:
Q.rear=(Q.rear+1)%MaxSize
。 - 队列长度:
(Q.rear+MaxSize-Q.front)%MaxSize
。
那么,循环队列队空和队满的判断条件是什么呢?显然,队空的条件是Q.front==Q.rear
。若入队元素的速度快于出队元素的速度,则队尾指针很快就会赶上队首指针,如图(d1)所示,此时可以看出队满时也有Q.front==Q.rear
。循环队列出入队示意图如下图所示。
为了区分队空还是队满的情况,有三种处理方式(首选第1种方式):
1)牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是一种较为普遍的做法,约定以“队头指针在队尾指针的下一位置作为队满的标志”,如图(d2)所示。
- 队满条件:
(Q.rear+1)%MaxSize==Q.front
。 - 队空条件仍:
Q.front==Q.rear
。 - 队列中元素的个数:
(Q.rear-Q.front+MaxSize)% MaxSize
。
2)类型中增设表示元素个数的数据成员。这样,队空的条件为Q.size==0
;队满的条件为Q.size==MaxSize
。这两种情况都有Q.front==Q.rear
。
3)类型中增设tag数据成员,以区分是队满还是队空。tag 等于0时,若因删除导致Q.front==Q.rear
,则为队空;tag等于1时,若因插入导致Q.front==Q.rear
,则为队满。
3.循环队列的实现:
#include <iostream>
using namespace std;
#define MaxSize 8 //定义队列中元素的最大个数
template<typename T>
struct SqQueue {
T data[MaxSize]; //存放队列元素
int front, rear; //队头指针和队尾指针
};
template<class T>
class SqQueueClass {
private:
SqQueue<T> sqQueue;
public:
//缺省构造函数
SqQueueClass() {
this->InitQueue();
}
//初始化
void InitQueue() {
//初始化队首队尾指针
sqQueue.front = 0;
sqQueue.rear = 0;
}
//判断队空
bool isEmpty() {
if (sqQueue.front == sqQueue.rear)
return true;
return false;
}
//判断队满
bool isFull() {
if ((sqQueue.rear + 1) % MaxSize == sqQueue.front)
return true;
return false;
}
//入队
bool EnQueue(T x) {
if (this->isFull()) //队满则报错
return false;
sqQueue.data[sqQueue.rear] = x;
sqQueue.rear = (sqQueue.rear + 1) % MaxSize;
return true;
}
//出队
bool DeQueue(T &x) {
if (this->isEmpty()) //队空则报错
return false;
x = sqQueue.data[sqQueue.front];
sqQueue.front = (sqQueue.front + 1) % MaxSize;
return true;
}
//查看队列中所有元素
void display() {
if (this->isEmpty()) {
cout << "The SqQueue is empty!" << endl;
return;
}
cout << "all elements in SqQueue:\t";
int i = sqQueue.front;
int k = sqQueue.rear;
while (i != k) {
cout << sqQueue.data[i] << "\t";
i = (i + 1) % MaxSize;
}
cout << endl;
}
};
int main() {
//测试
SqQueueClass<int> sqQueueInstance;
int x;
for (int i = 0; i < 10; i++)
if (sqQueueInstance.EnQueue(i))
cout << "enqueue " << i << " ok!" << endl;
else
cout << "enqueue error! The SqQueue is full!" << endl;
sqQueueInstance.display();
for (int i = 0; i < 10; i++)
if (sqQueueInstance.DeQueue(x))
cout << "dequeue " << x << " ok!" << endl;
else
cout << "dequeue error! The SqQueue is empty!" << endl;
return 0;
}
运行结果
enqueue 0 ok!
enqueue 1 ok!
enqueue 2 ok!
enqueue 3 ok!
enqueue 4 ok!
enqueue 5 ok!
enqueue 6 ok!
enqueue error! The SqQueue is full!
enqueue error! The SqQueue is full!
enqueue error! The SqQueue is full!
all elements in SqQueue: 0 1 2 3 4 5 6
dequeue 0 ok!
dequeue 1 ok!
dequeue 2 ok!
dequeue 3 ok!
dequeue 4 ok!
dequeue 5 ok!
dequeue 6 ok!
dequeue error! The SqQueue is empty!
dequeue error! The SqQueue is empty!
dequeue error! The SqQueue is empty!
队列的链式存储结构
1.队列的链式存储:
队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点(注意与顺序存储的不同)。队列的链式存储如图所示。
队列的链式存储结构可描述为:
template<typename T>
struct LinkNode { //链式队列结点
T data;
LinkNode *next;
};
template<typename T>
struct LinkQueue { //链式队列
LinkNode<T> *front, *rear; //队列的队头和队尾指针
};
- 当
Q.front==NULL
且Q.rear==NULL
时,链式队列为空。 - 出队时,首先判断队是否为空,若不空,则取出队头元素,将其从链表中摘除,并让Q.front指向下一个结点(若该结点为最后一个结点,则置Q.front和 Q.rear都为NULL)。
- 入队时,建立一个新结点,将新结点插入到链表的尾部,并改让Q.rear指向这个新插入的结点(若原队列为空队,则令Q.front也指向该结点)。
不难看出,不带头结点的链式队列在操作上往往比较麻烦,因此通常将链式队列设计成一个带头结点的单链表,这样插入和删除操作就统一了,如图所示。
用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满且产生溢出的问题。另外,假如程序中要使用多个队列,与多个栈的情形一样,最好使用链式队列,这样就不会出现存储分配不合理和“溢出”的问题。
2.链式队列的实现:
#include <iostream>
using namespace std;
template<typename T>
struct LinkNode { //链式队列结点
T data;
LinkNode *next;
};
template<typename T>
struct LinkQueue { //链式队列
LinkNode<T> *front, *rear; //队列的队头和队尾指针
};
template<class T>
class LinkQueueClass {
private:
LinkQueue<T> linkQueue;
public:
//缺省构造函数
LinkQueueClass() {
this->InitQueue();
}
//初始化
void InitQueue() {
linkQueue.front = linkQueue.rear = new LinkNode<T>; //建立头结点
linkQueue.front->next = NULL;
}
//队判空
bool IsEmpty() {
if (linkQueue.front == linkQueue.rear)
return true;
return false;
}
//入队
void EnQueue(T x) {
LinkNode<T> *p = new LinkNode<T>; //创建新结点,插入到链尾
p->data = x;
p->next = NULL;
linkQueue.rear->next = p;
linkQueue.rear = p;
}
//出队
bool DeQueue(T &x) {
if (this->IsEmpty()) //空队
return false;
LinkNode<T> *p = linkQueue.front->next;
x = p->data;
linkQueue.front->next = p->next;
if (linkQueue.rear == p)
linkQueue.rear = linkQueue.front; //若原队列中只有一个结点,将队尾指针指向头结点
delete p;
return true;
}
//查看队列中所有元素
void display() {
if (this->IsEmpty()) {
cout << "The LinkQueue is empty!" << endl;
return;
}
cout << "all elements in LinkQueue:\t";
LinkNode<T> *p = linkQueue.front->next;
while (p != NULL) {
cout << p->data << "\t";
p = p->next;
}
cout << endl;
}
};
int main() {
//测试
LinkQueueClass<int> linkQueueInstance;
int x;
for (int i = 0; i < 10; i++) {
linkQueueInstance.EnQueue(i);
cout << "enqueue " << i << " ok!" << endl;
}
linkQueueInstance.display();
for (int i = 0; i < 13; i++)
if (linkQueueInstance.DeQueue(x))
cout << "dequeue " << x << " ok!" << endl;
else
cout << "dequeue error! The LinkQueue is empty!" << endl;
linkQueueInstance.display();
return 0;
}
运行结果
enqueue 0 ok!
enqueue 1 ok!
enqueue 2 ok!
enqueue 3 ok!
enqueue 4 ok!
enqueue 5 ok!
enqueue 6 ok!
enqueue 7 ok!
enqueue 8 ok!
enqueue 9 ok!
all elements in LinkQueue: 0 1 2 3 4 5 6 7 8 9
dequeue 0 ok!
dequeue 1 ok!
dequeue 2 ok!
dequeue 3 ok!
dequeue 4 ok!
dequeue 5 ok!
dequeue 6 ok!
dequeue 7 ok!
dequeue 8 ok!
dequeue 9 ok!
dequeue error! The LinkQueue is empty!
dequeue error! The LinkQueue is empty!
dequeue error! The LinkQueue is empty!
The LinkQueue is empty!
双端队列
双端队列是指允许两端都可以进行入队和出队操作的队列,如图所示。其元素的逻辑结构仍是线性结构。将队列的两端分别称为前端和后端,两端都可以入队和出队。
在双端队列进队时,前端进的元素排列在队列中后端进的元素的前面,后端进的元素排列在队列中前端进的元素的后面。在双端队列出队时,无论是前端还是后端出队,先出的元素排列在后出的元素的前面。
输出受限的双端队列:允许在一端进行插入和删除,但在另一端只允许插入的双端队列称为输出受限的双端队列,如图所示。
输入受限的双端队列:允许在一端进行插入和删除,但在另一端只允许删除的双端队列称为输入受限的双端队列,如图所示。若限定双端队列从某个端点插入的元素只能从该端点删除,则该双端队列就蜕变为两个栈底相邻接的栈。