5.2 队列的链式结构
文章目录
5.2.1 队列链式结构简介
相比较与循环队列的顺序存储结构,链式结构多了一条指向下一结点的链,和单链表结构是类似的,只是链式队列只能"头出尾进",下图是一个链式队列的简单结构图:
引入了一个头结点不存储任何东西,front指针指向头结点,rear指针指向队列的最后一个元素。
下面是涉及到有关链式队列操作的表:
操作函数 | 操作结果 |
---|---|
InitQueue(LinkQueue *Q) | 初始化链队列 |
DestroyQueue(LinkQueue *Q) | 销毁队列 |
ClearQueue(LinkQueue *Q) | 清空队列 |
QueueEmpty(LinkQueue Q) | 判断队列是否为空 |
QueueLength(LinkQueue Q) | 求链队列长度 |
GetHead(LinkQueue Q,QElemType *e) | 获取头元素,并且存入指针e |
EnQueue(LinkQueue *Q,QElemType e) | 入队 |
DeQueue(LinkQueue *Q,QElemType *e) | 出队 |
QueueTraverse(LinkQueue Q) | 遍历并且打印队列 |
5.2.2 队列链式结构定义
链队列的结构定义和单链表类似,一个链式队列包含数个结点和指向头结点,尾结点的指针(front和rear
),一个结点包含存储的数据data
和指向下一结点的链next
。
代码如下:
typedef struct QNode{ //结点结构
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{ //队列结构
QueuePtr front,rear; //队头 队尾指针
}LinkQueue;
QNode为结点类,LinkQueue为链队列类,队头,队尾指针都是结点指针类型。
5.2.3 初始化链队列
一个初始的链队列中不存储任何数据,头尾指针都指向头结点,下图是一个初始化的链队列:
千万要注意:头结点不等于队头,头结点不存储任何数据,队头是头结点的下一结点,即Q->front->next
。
代码如下:
/*初始化链队列*/
/*Q->front 是头结点不存任何东西 Q->front->next表示队头*/
Status InitQueue(LinkQueue *Q){
Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q->front)
exit(OVERFLOW);
Q->front->next = NULL;
return OK;
}
5.2.4 销毁队列和清空队列
销毁队列和清空队列是不同的,销毁队列连同头结点(那个不存储数据的结点),一起删除。而清空队列不删除头结点,只是删除存储数据的结点。
1. 销毁队列
代码如下:
/*销毁队列*/
Status DestroyQueue(LinkQueue *Q){
/*从头结点开始一直删除到最后 直到链队列不存在为止*/
while(Q->front){
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
return OK;
}
其中的Q->rear
只是起到一个暂时记录Q->front->next
结点的作用。第一次循环:Q->rear 记录了队头的位置,然后free(Q->front)
释放头结点,最后将头指针指向队头的位置(头结点的下一位置)。随后的循环都以这样的形式进行,从头结点开始删除,直到队列不存在为止。
2. 清空队列
代码如下:
/*清空队列*/
Status ClearQueue(LinkQueue *Q){
QueuePtr p,q;
Q->rear = Q->front;
p = Q->front->next;
Q->front->next = NULL;
/*上面头尾结点都指向null 最后再将队列中间的结点一个个释放*/
while(p){
q=p;
p=p->next;
free(q);
}
return OK;
}
清空队列,首先将尾指针指向头结点位置Q-rear = Q->front
,然后将队头元素赋值给指针p,即p = Q->front->next
,然后再将头结点的next指针赋值为空,这就让头尾结点都变成了初始化的时候,但是中间还有很多队列结点等待释放,这便交给了下面的while循环。
5.2.5 判断链队列是否为空和获取链队列长度
1. 判断链队列是否为空
当头指针和尾指针在同一个位置时,链队列为空,代码如下:
/*判断队列是否为空*/
Status QueueEmpty(LinkQueue Q){
if(Q.front==Q.rear)
return TRUE;
else
return FALSE;
}
2. 获取链队列长度
先使p=Q.front
,让p指向头结点,只要Q.rear!=p
就使p一直往下移动,并且让i+1,记录长度。
代码如下:
/*求队列长度*/
int QueueLength(LinkQueue Q){
int i = 0;
QueuePtr p;
p = Q.front;
while(Q.rear!=p){
i++;
p=p->next;
}
return i;
}
5.2.6 获得链队列队头元素
只要通过头指针就可以轻易获取到队列头元素,代码如下:
/*获取头元素*/
Status GetHead(LinkQueue Q,QElemType *e){
QueuePtr p;
if(Q.front==Q.rear) //队空
return ERROR;
p = Q.front->next;
*e = p->data;
return OK;
}
5.2.7 入队和出队
1. 入队
链队列入队和单链表的插入是类似的,只不过链队列只能从队尾插入,入队的示意图如下:
步骤如下:
- 先生成一个新结点s,并且将数据存入
- 然后让s结点的next指针指向NULL,即
s->next=NULL
- 再让rear指针处结点的next指针指向s,即
Q->rear->next = s
- 最后将rear指针移动到结点s处
Q->rear = s
,完成入队
代码如下:
/*链队列入队*/
Status EnQueue(LinkQueue *Q,QElemType e){
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if(!s)
exit(OVERFLOW);
s->data = e;
s->next = NULL;
Q->rear->next = s; //将s赋值给队尾后继元素
Q->rear = s; //最后将s赋值给队尾指针
return OK;
}
2. 出队
出队都是在队头完成的,下面是出队示意图:
步骤如下:
- 首先判断队列是否为空
- 将要出队的队头赋值给p,即
p=Q->front->next
- 然后再将p的后继变成队头,即
Q->front->next = p->next
- 释放结点p
注意,如果删除的队头就是队尾结点,将rear指向头结点。
代码如下:
/*链队列出队*/
Status DeQueue(LinkQueue *Q,QElemType *e){
QueuePtr p;
if(Q->front == Q->rear) //队空
return ERROR;
p = Q->front->next; //将队头将要删除的元素暂时储存在p
*e = p->data;
Q->front->next = p->next; //将p的后继变为队头,之前的队头出队
if(Q->rear == p) //若删除的队头就是队尾,将rear指向头结点
Q->rear = Q->front;
free(p);
return OK;
}
5.2.8 遍历打印链队列
代码如下:
/*遍历队列 从队头开始到队尾*/
Status visit(QElemType c){
printf("%d ",c);
return OK;
}
Status QueueTraverse(LinkQueue Q){
QueuePtr p;
p = Q.front->next; //p是队头
while(p){
visit(p->data);
p = p->next;
}
printf("\n");
return OK;
}
5.2.9 测试代码
测试代码
int main(){
int i;
QElemType d;
LinkQueue q;
i=InitQueue(&q);
if(i)
printf("成功地构造了一个空队列!\n");
printf("是否空队列?%d(1:空 0:否) ",QueueEmpty(q));
printf("队列的长度为%d\n",QueueLength(q));
EnQueue(&q,-5);
EnQueue(&q,5);
EnQueue(&q,10);
printf("插入3个元素(-5,5,10)后,队列的长度为%d\n",QueueLength(q));
printf("是否空队列?%d(1:空 0:否) ",QueueEmpty(q));
printf("队列的元素依次为:");
QueueTraverse(q);
i=GetHead(q,&d);
if(i==OK)
printf("队头元素是:%d\n",d);
DeQueue(&q,&d);
printf("删除了队头元素%d\n",d);
i=GetHead(q,&d);
if(i==OK)
printf("新的队头元素是:%d\n",d);
ClearQueue(&q);
printf("清空队列后,q.front=%u q.rear=%u q.front->next=%u\n",q.front,q.rear,q.front->next);
DestroyQueue(&q);
printf("销毁队列后,q.front=%u q.rear=%u\n",q.front, q.rear);
system("pause");
return 0;
}
运行结果
成功地构造了一个空队列!
是否空队列?1(1:空 0:否) 队列的长度为0
插入3个元素(-5,5,10)后,队列的长度为3
是否空队列?0(1:空 0:否) 队列的元素依次为:-5 5 10
队头元素是:-5
删除了队头元素-5
新的队头元素是:5
清空队列后,q.front=13060336 q.rear=13060336 q.front->next=0
销毁队列后,q.front=0 q.rear=0
请按任意键继续. . .
5.2.10 完整代码
下面附上完整代码:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20
typedef int Status;
typedef int QElemType;
typedef struct QNode{ //结点结构
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{ //队列结构
QueuePtr front,rear; //队头 队尾指针
}LinkQueue;
/*初始化链队列*/
/*Q->front 是头结点不存任何东西 Q->front->next表示队头*/
Status InitQueue(LinkQueue *Q){
Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q->front)
exit(OVERFLOW);
Q->front->next = NULL;
return OK;
}
/*销毁队列*/
Status DestroyQueue(LinkQueue *Q){
/*从头结点开始一直删除到最后 直到链队列不存在为止*/
while(Q->front){
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
return OK;
}
/*清空队列*/
Status ClearQueue(LinkQueue *Q){
QueuePtr p,q;
Q->rear = Q->front;
p = Q->front->next;
Q->front->next = NULL;
/*上面头尾结点都指向null 最后再将队列中间的结点一个个释放*/
while(p){
q=p;
p=p->next;
free(q);
}
return OK;
}
/*判断队列是否为空*/
Status QueueEmpty(LinkQueue Q){
if(Q.front==Q.rear)
return TRUE;
else
return FALSE;
}
/*求队列长度*/
int QueueLength(LinkQueue Q){
int i = 0;
QueuePtr p;
p = Q.front;
while(Q.rear!=p){
i++;
p=p->next;
}
return i;
}
/*获取头元素*/
Status GetHead(LinkQueue Q,QElemType *e){
QueuePtr p;
if(Q.front==Q.rear) //队空
return ERROR;
p = Q.front->next;
*e = p->data;
return OK;
}
/*链队列入队*/
Status EnQueue(LinkQueue *Q,QElemType e){
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if(!s)
exit(OVERFLOW);
s->data = e;
s->next = NULL;
Q->rear->next = s; //将s赋值给队尾后继元素
Q->rear = s; //最后将s赋值给队尾指针
return OK;
}
/*链队列出队*/
Status DeQueue(LinkQueue *Q,QElemType *e){
QueuePtr p;
if(Q->front == Q->rear) //队空
return ERROR;
p = Q->front->next; //将队头将要删除的元素暂时储存在p
*e = p->data;
Q->front->next = p->next; //将p的后继变为队头,之前的队头出队
if(Q->rear == p) //若删除的队头就是队尾,将rear指向头结点
Q->rear = Q->front;
free(p);
return OK;
}
/*遍历队列 从队头开始到队尾*/
Status visit(QElemType c){
printf("%d ",c);
return OK;
}
Status QueueTraverse(LinkQueue Q){
QueuePtr p;
p = Q.front->next; //p是队头
while(p){
visit(p->data);
p = p->next;
}
printf("\n");
return OK;
}
int main(){
int i;
QElemType d;
LinkQueue q;
i=InitQueue(&q);
if(i)
printf("成功地构造了一个空队列!\n");
printf("是否空队列?%d(1:空 0:否) ",QueueEmpty(q));
printf("队列的长度为%d\n",QueueLength(q));
EnQueue(&q,-5);
EnQueue(&q,5);
EnQueue(&q,10);
printf("插入3个元素(-5,5,10)后,队列的长度为%d\n",QueueLength(q));
printf("是否空队列?%d(1:空 0:否) ",QueueEmpty(q));
printf("队列的元素依次为:");
QueueTraverse(q);
i=GetHead(q,&d);
if(i==OK)
printf("队头元素是:%d\n",d);
DeQueue(&q,&d);
printf("删除了队头元素%d\n",d);
i=GetHead(q,&d);
if(i==OK)
printf("新的队头元素是:%d\n",d);
ClearQueue(&q);
printf("清空队列后,q.front=%u q.rear=%u q.front->next=%u\n",q.front,q.rear,q.front->next);
DestroyQueue(&q);
printf("销毁队列后,q.front=%u q.rear=%u\n",q.front, q.rear);
system("pause");
return 0;
}