本系列文章是笔者复习自用,参考教材为严蔚敏编著的《数据结构(C语言版)》,如能对大家有所帮助实属荣幸
本篇的内容主要是队列的链式表示和实现,主要包括队列的定义及其常用函数的实现,并补充了循环队列的内容。
5.1 队列的定义
和栈相反,队列是一种先进先出的线性表,只能在表的一端进行插入,并且只能在另一端进行删除,队列的定义和我们平常见到的排队相似,最早进入队列的元素最早离开。如果进入队列的顺序是a,b,c,d,e,那么离开队列的顺序也是a,b,c,d,e。
在队列中,允许插入的一端叫队尾,允许删除的一端叫队头,示意图如下:
5.2 链式队列的表示和实现
和线性表类似,队列也有两种表示形式,分别是顺序和链式,这里我们使用链式队列。
为方便操作,我们给链队列加了一个头结点,只起结构作用,而不包含数据。同时,我们定义了两个指针——头指针front和尾指针rear,front指向头结点,而rear指向最新插入的元素,也就是说,只有front指向的元素可以被删除,而每一个刚进入队列的元素都会被rear所指。
1.链队列的表示
链队列使用了两个结构体,QNode和LinkQueue。
结构体QNode表示列表中储存每一个元素的结点,每一个结点有一个数据域和一个指向后驱结点的指针域;结构体LinkQueue表示整个列表,有一个头指针和·一个尾指针,分别指向头结点和储存列表中最新元素的结点,当两个指针相等时,列表为空。
typedef struct QNode{
ElemType data; //数据域
struct QNode *next; //指针域
}QNode,*QueuePtr;
typedef struct{
QueuePtr front;//队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
2.链队列的初始化
在链队列的初始化中,首先需要给头结点申请空间,使尾指针和头指针相等,并使他们的后驱为空,以表明该为空表。
Status InitQueue(LinkQueue &Q){
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));//表头也是一个节点,但无数据域
if (!Q.front) exit(ERROR);
Q.front->next = NULL;
return OK;
}
3.链队列的销毁
在链队列的销毁中,需要从表头开始释放空间,直到所有空间都被释放。在教材中,释放空间的过程中使用队尾指针当做中介,如下:
Status DestroyQueue(LinkQueue &Q){
while (Q.front)
{
Q.rear = Q.front->next; //每次都销毁队头第一个元素
free(Q.front);
Q.front = Q.rear;
}
return OK;
}
笔者觉得这虽然节约了空间,但不利于理解,应该另外定义一个指针,而不应移动表尾指针,如下:
Status DestroyQueue(LinkQueue &Q){
QueuePtr p;
while (Q.front)
{
p = Q.front->next;
free(Q.front);
Q.front = p;
}
return OK;
}
4.入队操作
入队操作指的是在队列的末尾插入一个新的结点,并使其成为队尾。
其算法思想是,先创建一个新的结点,给新结点的数据域赋值,然后将其设为队尾的后驱,最后让其成为队尾。
Status EnQueue(LinkQueue &Q, ElemType e){
QNode *node = (QNode *)malloc(sizeof(QNode));
if (!node) exit(ERROR);//创建失败则退出
node->data = e;
Q.rear->next = node;
Q.rear = node;//使其成为新的队尾
node->next = NULL;
return OK;
}
5.出队操作
出队操作指的是在队列的头部删去一个结点,并取出它的值。
其算法思想是,新建一个指针指向待删去的结点,修改头结点的后驱使其等于新指针的后驱,取出值后释放新指针所指的空间。
ElemType DeQueue(LinkQueue &Q){
ElemType e;//表示待删除结点的值
QueuePtr p = Q.front->next;//从第一个元素开始
if (Q.front == Q.rear) exit(ERROR);
Q.front->next = p->next;
e = p->data;
free(p);
return e;
}
5.3 循环队列——队列的顺序表示和实现
和顺序栈类似,在队列的顺序存储结构中,除了用及一组地址连续的存储单元依次存放从队列头到队列尾的元素之外,尚需附设两个指针front和rear分别指示队列头元素和队列尾元素的位置。为了方便操作,我们约定:初始化建空队列时,令front=rear=0,每当插入新的元素时,队尾指针加1;每当删除队头元素时,队头指针加1。因此,在非空队列中,头指针始终指向队列头元素,尾指针始终指向队列尾元素的下一个位置,如下图所示:
图1:空队列;
图2:插入j1,j2,j3相继入队之后;
图3:j1,j2相继出队;
图4:j4,j5,j6相继入队之后,j3,j4相继出队。
由图可知,当队列处于图4状态下,已无法再继续插入元素,否则会因数组越界而导致代码被破坏,但此时队列空间又没被占满,所以我们通常将循环队列看作一个环。但此时又遇到一个问题,如果队列的每一个空间都投入使用,那么队空和队满都可表示为Q.front==Q.rear,如下图:
为了解决这个问题,我们一般会空出一个位置,这样,我米就可以通过Q.front==Q.rear来判断队空,通过(Q.rear+1)%8==Q.front来判断队满,如下图:
循环队列的相关函数与普通队列差别不大,这里就不贴出代码,只需记住循环队列判断队满的条件为 (Q.rear+1)%8==Q.front。