一、队列的基本概念
队列(Queue)也是一种操作受限的线性表,只允许在表的一端进行插入操作,而在另一端进行删除操作,跟我们日常生活中的排队是一致的,操作的特性是先进先出(FIFO)。
允许插入(也称入队,进队)的一端称为队尾,允许删除(出队)的一端称为队头。
二、队列的顺序存储结构
2.1 顺序队列
2.1.1 定义说明
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针front指向队头元素,队尾指针rear指向队尾元素的下一个位置(当然也可以指向队尾,根据自己的情况来即可)。
2.1.2 函数说明
顺序存储类型
typedef struct {
Elemtype data[Maxsize]; //存放队列元素
int front; //队-头指针
int rear; //队-尾指针(我这里设置的是: 指向队尾元素的下一个位置)
}SqQueue;
初始化
bool InitQueue(SqQueue& Q)
{
Q.front = 0;
Q.rear = 0;
return true;
}
入队
bool EnQueue(SqQueue& Q, Elemtype e)
{
if (Q.rear == Maxsize)
return false; //队满则报错
else
{
Q.data[Q.rear] = e; //将元素送入队尾
Q.rear = Q.rear + 1; //队尾指针加1,指向队尾的下一个位置
return true;
}
}
出队
bool DeQueue(SqQueue& Q, Elemtype& x)
{
if (Q.rear == Q.front)
return false; //队空则报错
else
{
x = Q.data[Q.front]; //当前队头元素记为x
Q.front = Q.front + 1; //队头指针加1,往后移
return true;
}
}
打印队列
void PrintQueue(SqQueue& Q)
{
cout << "从队列到队尾的元素数据是:";
for (int i = Q.front; i < Q.rear; i++)
{
cout << Q.data[i] << " ";
}
cout << endl;
}
主函数
#include<iostream>
using namespace std;
#define Elemtype int
#define Maxsize 10
int main()
{
SqQueue Q;
InitQueue(Q);
int n;
//(1)入队数据
cout << "(最多10个)想要入队列几个数据:" << endl;
cin >> n;
for (int i = 0; i < n; i++)
{
int j;
cin >> j;
EnQueue(Q, j);
}
PrintQueue(Q);
//(2)出队
int m;
DeQueue(Q, m);
PrintQueue(Q);
cout << "出队元素值为:" << m << endl;
system("pause");
return 0;
}
2.1.3 思考小结
(1)可以看到:当Q.rear == Q.front成立,可以用来判断队列是否为空;
(2)进队操作:队不满时,先送值到队尾,再讲队尾指针加1;出队操作:队不空时,先送出队头元素,再讲队头指针加1;
(3)但能否用Q.rear==Maxsize来作为队列满的情况呢?即使我这里是用的这个,但其实显然不能这样做。假溢出:当元素被插入到数组的中下标的最大的位置上之后,队列的空间空间就用完了,尽管此时数组的低端还有空闲空间,也无法再入队新的元素了。(那该如何解决呢,就用到我们马上要介绍的循环队列了)
2.2 循环队列
2.2.1 定义说明
我们在前面指出了顺序队列的缺点,那我们讲顺序队列臆造成一个环状的空间,把存储队列元素的表从逻辑上视为一个环。为达到循环的效果,我们使用“除法取余运算%”来实现。
为了区分队空与队满的情况,有三种处理方式:
(1)牺牲一个队列单元,则队满的条件是:(Q.rear+1)%Maxsize==Q.front;队列中元素的个数是:(Q.rear - Q.front + Maxsize) % Maxsize。
(2)类型中增设表示元素个数的 size 数据成员。这样队空Q.size==0,队满Q.size==Maxsize;
(3)类型中增设 tag 数据成员,当tag==0时,若因为删除导致此时Q.rear == Q.front,则队空;tag==1时,若因为插入导致此时Q.rear == Q.front,则队满。
2.2.2 函数说明(我是用上述的第一种方法)
入队
bool EnQueue(SqQueue& Q, Elemtype e)
{
if ((Q.rear + 1) % Maxsize == Q.front)
return false; //队满则报错
else
{
Q.data[Q.rear] = e; //将元素送入队尾
Q.rear = (Q.rear + 1) % Maxsize; //队尾指针加1取模(达到循环的效果),指向队尾的下一个位置
return true;
}
}
出队
bool DeQueue(SqQueue& Q, Elemtype& x)
{
if (Q.rear == Q.front)
return false; //队空则报错
else
{
x = Q.data[Q.front]; //当前队头元素记为x
Q.front = (Q.front + 1) % Maxsize; //队头指针加1取模(达到循环的效果),往后移
return true;
}
}
打印
void PrintQueue(SqQueue& Q)
{
int len = (Q.rear - Q.front + Maxsize) % Maxsize;
cout << "从队头到队尾共有" << len << "个元素,分别是";
for (int i = 0; i < len; i++)
{
int j = (Q.front + i) % Maxsize;
cout << Q.data[j] << " ";
}
cout << endl;
}
主函数
int main()
{
SqQueue Q;
InitQueue(Q);
//(1)入队数据
cout << "入队9个数据(会少使用一个队列单元):" << endl;
for (int i = 0; i < Maxsize-1; i++)
{
int j;
cin >> j;
EnQueue(Q, j);
}
PrintQueue(Q);
//(2)出队
int m;
DeQueue(Q, m);
PrintQueue(Q);
cout << "出队元素值为:" << m << endl;
//(3)再入队1个数据
EnQueue(Q, 10);
PrintQueue(Q);
system("pause");
return 0;
}
2.2.3 思考小结
只有在入队,出队的操作上与顺序表有些区别,是为了达到循环的效果。只是逻辑上循环,实际上也还是一连段的空间。
三、队列的链式存储结构
3.1 定义说明
队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点。
3.2 函数说明
(1)链式存储类型描述
//链式存储类型
typedef struct LinkNode { //链式队列结点
Elemtype data;
struct LinkNode* next;
}LinkNode;
typedef struct { //链式队列
LinkNode* front, * rear; //队列的队头和队尾指针
}LinkQueue;
(2)初始化(带头结点)
//初始化
void InitQueue(LinkQueue& Q)
{
LinkNode* p;
p = new LinkNode; //创建头结点
if (!p)
{
cout << "分配内存失败" << endl;
return;
}
p->next = NULL;
Q.front = Q.rear = p; //队列的头指针和尾指针都指向头结点
}
(3)判断是否为空
//判断队列是否为空
bool IsEmpty(LinkQueue Q)
{
if (Q.front == Q.rear)
return true; //为空
else
return false;
}
(4)入队
//入队(只能从队尾插入)
void EnQueue(LinkQueue& Q, Elemtype e)
{
LinkNode* p;
p = new LinkNode;
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
}
(5)出队
//出队(只有队头元素出队)
bool DeQueue(LinkQueue& Q, Elemtype& e)
{
if (IsEmpty(Q))
return false; //队列为空
LinkNode* p;
p = Q.front->next;
if (Q.front->next == Q.rear) //若原队列只有一个结点,删除后变空
Q.front = Q.rear;
e = p->data;
Q.front->next = p->next;
delete p;
}
(6)打印
//打印队列
void PrintQueue(LinkQueue& Q)
{
int length = 0;
LinkNode* p = Q.front->next;
cout << "此队列为:";
while (p != NULL)
{
cout << p->data << " ";
length++;
p = p->next;
}
cout << "队列长度为" << length << endl;
}
(7)主函数
#include<iostream>
using namespace std;
#define Elemtype int
int main()
{
LinkQueue Q;
InitQueue(Q);
//入队
int n;
cout << "想要入队列几个数据:" << endl;
cin >> n;
for (int i = 0; i < n; i++)
{
int j;
cin >> j;
EnQueue(Q, j);
}
PrintQueue(Q);
//出队
int e;
DeQueue(Q, e);
cout << "队头元素" << e << "出队" << endl;
PrintQueue(Q);
system("pause");
return 0;
}
3.3 思考小结
因为有两个指针,头指针和尾指针,便于直接在队头执行删除,队尾执行插入操作,而不用像单链表那样需要遍历一次,更加方便,只是插入删除的位置受限而已。