队列
- 队列:一种先进先出(FIFO first in first out)的线性表。
- 允许插入的一端为队尾,允许删除的一端为队头
- 应用:操作系统作业排队
链队列
- 链队列实质上是一种只能尾进头出的单链表。
- 核心:找到头指针和尾指针即确定了链队列(后续链队列的操作皆围绕这俩指针展开)
1.链队列的存储结构(一个结点结构体,一个链队指针结构体)
typedef struct LinkedNode{
int data;
struct LinkedNode* next;
}*linkNodePtr;//结点
typedef struct linkedQueue{
linkNodePtr front;//指向的是结点,而非int型
linkNodePtr rear;
}*linkedQueuePtr;//链队指针
2.链队列的初始化
/*链队的初始化(不需要传参,需要返回值)*/
linkedQueue initLinedQueue{
//创建头结点并初始化
LinkNodePtr tempHeader=(LinkNodePtr)malloc(sizeof(struct LinkedNode));
tempHeader->data=-1;
tempHeader->next=NULL;
//创建空队列
linkedQueuePtr resultPtr=(linkedQueuePtr)malloc(sizeof(struct linkedQueue));
resultPtr->front=tempHeader;
resultPtr->rear=tempHeader;
//返回初始化后的队列
return resultPtr;
}
初始化之后如图所示:
3.图解测试样例:
enqueue(tempQueuePtr,10);语句执行之后:
执行完enqueue(tempQueuePtr,90);之后:
第一次执行dequeue(tempQueuePtr); 语句之后:
执行完第三次dequeue(tempQueuePtr);之后:
当执行第四次 dequeue(tempQueuePtr); 语句时,paraQueuePtr->front->next=p->next;的执行会让front所指向的头结点tempHeader->next重新为空,且因为此时front和rear相遇,所以执行
执行完毕之后,rear又会重新指向头结点
4.完整代码
#include<stdio.h>
#include<malloc.h>
/*队列的链式存储结构*/
typedef struct LinkedNode{
int data;
struct LinkedNode* next;
}*linkNodePtr;//结点
typedef struct linkedQueue{
linkNodePtr front;//指向的是结点,而非int型
linkNodePtr rear;
}*linkedQueuePtr;//链队指针
/*链队的初始化(不需要传参,需要返回值)*/
linkedQueuePtr initLinkedQueue(){
//创建头结点并初始化
linkNodePtr tempHeader=(linkNodePtr)malloc(sizeof(struct LinkedNode));
tempHeader->data=-1;
tempHeader->next=NULL;
//创建空队列(头尾指针都指向头结点)
linkedQueuePtr resultPtr=(linkedQueuePtr)malloc(sizeof(struct linkedQueue));
resultPtr->front=tempHeader;
resultPtr->rear=tempHeader;
//返回初始化后的队列
return resultPtr;
}
/*打印链队*/
void outputQueue(linkedQueuePtr paraQueuePtr){
linkNodePtr p=paraQueuePtr->front->next;
while(p!=NULL){
printf("%d ",p->data);
p=p->next;
}//Of while
printf("\r\n");
}
/*链队的入队(需传参2个,无需返回值)*/
void enqueue(linkedQueuePtr paraQueuePtr,int paraElement){
//创建新入队元素的结点
linkNodePtr tempNode=(linkNodePtr)malloc(sizeof(struct LinkedNode));
tempNode->data=paraElement;
tempNode->next=paraQueuePtr->rear->next;//tempNode->next=NULL;
//新节点入队
paraQueuePtr->rear->next=tempNode;
printf("%d has enqueued.\r\n",tempNode->data);
//更新尾指针
paraQueuePtr->rear=tempNode;//paraQueuePtr->rear++;
}
/*链队的出队(需要传参,返回出队元素)*/
int dequeue(linkedQueuePtr paraQueuePtr){
//检查队列空满情况
if(paraQueuePtr->front==paraQueuePtr->rear) {
printf("The queue is empty.\r\n");
return -1;
}
//改变指针指向
linkNodePtr p=paraQueuePtr->front->next;
paraQueuePtr->front->next=p->next;
int outValue=p->data;
printf("%d has dequeued.\r\n",outValue);
if(paraQueuePtr->rear==p){
paraQueuePtr->rear=paraQueuePtr->front;
}
//删除出队元素结点
free(p);
//返回出队元素值 (因为结点会被删除,所以需要提前保存它的值)
return outValue;
}
/*测试*/
void test(){
linkedQueuePtr tempQueuePtr=initLinkedQueue();
printf("---- enqueueTest begins. ----\r\n");
enqueue(tempQueuePtr,10);
enqueue(tempQueuePtr,30);
enqueue(tempQueuePtr,50);
enqueue(tempQueuePtr,90);
printf("After enqueueing,the queue is:");
outputQueue(tempQueuePtr);
printf("---- enqueueTest ends. ----\r\n");
printf("---- dequeue begins. ----\r\n");
dequeue(tempQueuePtr);
dequeue(tempQueuePtr);
dequeue(tempQueuePtr);
dequeue(tempQueuePtr);
printf("After dequeueing,the queue is:");
outputQueue(tempQueuePtr);
printf("After another dequeue,the result is:");
dequeue(tempQueuePtr);
printf("---- dequeueTest ends. ----\r\n");
}
int main(){
test();
return 0;
}
运行结果:
问题回答
1.运行过程中,哪些变量分配在相邻的空间?
结构体成员变量
2.局部变量的空间能否重复利用?举例说明。
不能,因为局部变量在函数执行完毕之后就会被释放
如在enqueue函数中,tempNode是局部变量,当enqueue执行完毕之后,它就”消失“了
3.指针的地址和值的区别是什么?
指针的地址是指指针变量的地址(教室)
指针的值是指针变量存储的值(教室里的(其他教室的)钥匙)
循环队列
- 循环队列:队列的一种头尾相接的顺序存储结构(可解决队列顺序存储造成的假溢出问题)
- 解决假溢出:”模运算“可实现头尾指针在顺序表空间中以头尾衔接的方式进行”循环移动“
- 区别队满队空:少用一个元素空间或设置标志位
1.循环队列的存储表示
typedef struct CircleIntQueue{
int data[TOTAL_SPACE];//开辟连续空间存储队列元素
//使用整型变量指示队列头元素和队列尾元素
int head;
int tail;
}*CircleIntQueuePtr;
#include <stdio.h>
#include <malloc.h>
#define TOTAL_SPACE 5
/*循环队列的存储表示*/
typedef struct CircleIntQueue{
int data[TOTAL_SPACE];//开辟连续空间存储队列元素
//使用整型变量指示队列头元素和队列尾元素
int front;
int rear;
}*CircleIntQueuePtr;
/*初始化队列(无需传参,需返回值)*/
CircleIntQueuePtr initQueue(){
CircleIntQueuePtr resultPtr=(CircleIntQueuePtr)malloc(sizeof(struct CircleIntQueue));
resultPtr->front=0;
resultPtr->rear=0;
return resultPtr;
}
/*入队(需要两参,无需返回值)*/
void enqueue(CircleIntQueuePtr paraQueue,int paraValue){
//判断队列是否已满
if((paraQueue->rear+1)%TOTAL_SPACE==paraQueue->front){
printf("The queue is full.\r\n");
return;
}
//元素入队,更新尾指针
paraQueue->data[paraQueue->rear%TOTAL_SPACE]=paraValue;//paraQueue->rear%TOTAL_SPACE实现循环
printf("%d enqueued.\r\n",paraQueue->data[paraQueue->rear%TOTAL_SPACE]);
paraQueue->rear++;
}
/*出队(需传参,需返回出队元素的值)*/
int dequeue(CircleIntQueuePtr paraQueue){
//判断队列是否为空
if(paraQueue->front==paraQueue->rear){
printf("The queue is empty.\r\n");
return -1;
}
//元素出队,更新头指针
int tempValue=paraQueue->data[paraQueue->front%TOTAL_SPACE];
paraQueue->front++;
//返回出队元素的值(因为front指针会更新,因此需要提前保存出队元素值)
return tempValue;
}
/*输出队列元素*/
void outputQueue(CircleIntQueuePtr paraQueue){
int i;
//判断队列是否为空
if(paraQueue->front==paraQueue->rear){
printf("The queue is empty.\r\n");
return;
}
printf("The elements in the queue:");
for(i=paraQueue->front;i<paraQueue->rear;i++){
printf("%d, ",paraQueue->data[i % TOTAL_SPACE]);//注意i % TOTAL_SPACE
}
printf("\r\n");
}
void CircleQueueTest(){
int i=10;
//初始化
CircleIntQueuePtr tempQueue=initQueue();
//入队
for (; i < 16; i ++) {
enqueue(tempQueue, i);
}
outputQueue(tempQueue);
//出队
for (i = 0; i < 6; i ++) {
printf("%d dequeued.\r\n", dequeue(tempQueue));
}
//再次入队
enqueue(tempQueue,8);
outputQueue(tempQueue);
}
void main(){
CircleQueueTest();
return;
}
运行结果:
图解CircleQueueTest():
1.初始化之后
2.元素入队:data[0]=10;rear++;
当rear=4时,执行如图所示语句
也就是说此时不允许再入队(因为需要预留一个空间来区别队空队满),所以后续enqueue会失败
3.元素出队:dequeue();front++;
当执行完第五条dequeue()之后,front=4,和rear一样都“指向”下标为4的位置
这样,不难看出此处的巧妙点是预留一个空间不用使得判断队满队空变得容易:
队空:paraQueue->front==paraQueue->rear
队满:(paraQueue->rear+1)%TOTAL_SPACE==paraQueue->front