C语言数据结构--队列
基本概念
队列是一种 先进先出(FIFO)的线性表
顾名思义,就和排队一样,先加入队伍的人先离开队伍,后加入队伍的人后离开队伍
在队尾插入元素,在队头删除元素
既然队列是线性表的一种,那么肯定也有两种存储形式
链队列 ——链式映像
循环队列——顺序映像
链队列
原理
用链表表示的队列简称为链队列
一个链队列需要两个分别指示对头和队尾的阵阵才能唯一确定。
基本操作
队列的链式存储结构
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERROR -1;
#define OK 1;
typedef char QElemType;
typedef struct QNode{ //结点类型
QElemType data; //队列中结点的数据域
struct QNode *next;//结点的指针域
}QNode,*QueuePtr;
typedef struct{ //链队列类型
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
初始化队列
示意图:
代码
LinkQueue *InitQueue(){
LinkQueue *Q = (LinkQueue *)malloc(sizeof(LinkQueue));
Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode));
Q->front->next = NULL;
printf("初始化队列成功!\n");
return Q;
}
入队列
入队列是从队尾插入元素,就像排队,肯定是从队尾排队。
如何找到队尾呢?
这就要用到队尾指针了。
示意图:
代码
LinkQueue *EnQueue(LinkQueue *Q, QElemType e){
QueuePtr p = (QueuePtr)malloc(sizeof(QNode));//为新插入的元素分配空间
if(!p){
printf("分配存储空间失败");
exit(-2);
}
p->data = e; //为p分配数据
p->next = NULL;//因为p在队尾,后面为空
Q->rear->next = p;//将新元素插入到队列末尾
Q->rear = p;//队尾指针移动位置
printf("%d入队列成功!\n",e);
return Q;
}
注意
⭐⭐⭐⭐⭐
代码里有一段代码
Q->rear->next = p;//将新元素插入到队列末尾
Q->rear = p;//队尾指针移动位置
我刚开始直接使用 Q->rear = p;错过了前面一步。前面的一步是必不可少的。
因为第一步代码是先将队列插入,第二步是移动队尾指针位置,两者并不重复。都很重要。
出队列
思考思路:
1)判断是否为空队列
2)出队列即从队头删除元素,是不是似曾相识的感觉,没错,出栈和出队列的原理几乎相同。
示意图:
程序
LinkQueue *DeQueue(LinkQueue *Q){
QElemType e;
QueuePtr q;
if(QueueEmpty(Q)){
printf("队列为空");
exit(-2);
}
q = Q->front->next;
e = q->data;
Q->front->next = q->next;
if(Q->rear == q){//如果q已经是最后一个指针,需要让队列尾指针等于队列头指针,即队列置空
Q->rear = Q->front;//这里无所谓顺序,也可以写成 Q->front = Q->rear
}
free(q);
printf("%d已经出队列!\n",e);
return Q;
}
注意:
⭐⭐⭐
当在删除最后一个元素后,需要使队列置空
if(Q->rear == q){//如果q已经是最后一个指针,需要让队列尾指针等于队列头指针,即队列置空
Q->rear = Q->front;//这里无所谓顺序,也可以写成 Q->front = Q->rear
}
销毁队列
程序
void DestroyQueue(LinkQueue *Q){
while(Q->front){
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
}
输出队列元素
程序
void display(LinkQueue *Q){
QueuePtr temp = Q->front;
if(QueueEmpty(Q)){
printf(“队列已空\n”);
exit(-2);
}
printf(“队列中的元素为:”);
while(temp != Q->rear){
printf("%d “,temp->next->data);
temp = temp->next;
}
printf(”\n");
}
链队列基本操作代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ERROR 0;
#define OK 1;
#define OVERFLOW -2;
typedef int QElemType;
typedef struct QNode{ //结点类型
QElemType data; //队列中结点的数据域
struct QNode *next;//结点的指针域
}QNode,*QueuePtr;
typedef struct{ //链队列类型
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
LinkQueue *InitQueue(){
LinkQueue *Q = (LinkQueue *)malloc(sizeof(LinkQueue));
Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode));
Q->front->next = NULL;
printf("初始化队列成功!\n");
return Q;
}
LinkQueue *EnQueue(LinkQueue *Q, QElemType e){
QueuePtr p = (QueuePtr)malloc(sizeof(QNode));//为新插入的元素分配空间
if(!p){
printf("分配存储空间失败");
exit(-2);
}
p->data = e; //为p分配数据
p->next = NULL;//因为p在队尾,后面为空
Q->rear->next = p;//将新元素插入到队列末尾
Q->rear = p;//队尾指针移动位置
printf("%d入队列成功!\n",e);
return Q;
}
bool QueueEmpty(LinkQueue *Q){
bool state = false;
if(Q->front == Q->rear){
state = true;
}
return state;
}
LinkQueue *DeQueue(LinkQueue *Q){
QElemType e;
QueuePtr q;
if(QueueEmpty(Q)){
printf("队列为空");
exit(-2);
}
q = Q->front->next;
e = q->data;
Q->front->next = q->next;
if(Q->rear == q){//如果q已经是最后一个指针,需要让队列尾指针等于队列头指针,即队列置空
Q->rear = Q->front;//这里无所谓顺序,也可以写成 Q->front = Q->rear
}
free(q);
printf("%d已经出队列!\n",e);
return Q;
}
void DestroyQueue(LinkQueue *Q){
while(Q->front){
Q->rear = Q->front->next;
free(Q->front);
Q->front = Q->rear;
}
}
void display(LinkQueue *Q){
QueuePtr temp = Q->front;
if(QueueEmpty(Q)){
printf("队列已空\n");
exit(-2);
}
printf("队列中的元素为:");
while(temp != Q->rear){
printf("%d ",temp->next->data);
temp = temp->next;
}
printf("\n");
}
int main(){
/*初始化栈*/
LinkQueue *Q = InitQueue();
/*入队列*/
Q = EnQueue(Q,1);
Q = EnQueue(Q,2);
Q = EnQueue(Q,3);
display(Q);
/*出队列*/
Q = DeQueue(Q);
Q = DeQueue(Q);
Q = DeQueue(Q);
display(Q);
return 0;
}
结果:
循环队列
原理
和顺序栈类似,在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素外,
还需要附设两个指针front和rear 分别指示队头和队尾元素的位置
同样和顺序栈类似,构建空队列时,令 front=rear=0;
当插入新的队列尾元素,rear+1
当删除队列头元素时,front+1(都是+1,详情见下表)
所以头指针始终指向队列头元素,尾指针始终指向队列尾元素的下一个位置
基本操作和顺序栈都是类似的,就不敲代码了,可以参考
https://blog.csdn.net/weixin_45468845/article/details/106475239
上面的结构有一点缺点,当处于最右边的那个图的状态时,不能再插入新的队尾元素,否则会因数组越界而导致程序代码被破坏。
如果像顺序栈那样再分配存储空间,前面空间将被浪费。那么该怎么办呢?
这是可以使用循环队列
原理图:
这里队列分配的最大存储空间是6
当队列满时,Q.front和Q.rear 指向同一个位置
但是也有一个问题,如何判断栈空还是栈满??
有两种方法:
1)另设一个标志位,区别队列是空还是满
2)少利用一个元素空间,约定“队头指针在队尾指针的下一位置”作为队列呈满的标志,就是还剩一个空间就认为满了
循环队列–队列的顺序存储结构
循环队列必须指定最大长度,如果不确定最大长度,可以使用链队列
#define MAXQSIXE 100//最大队列长度
typedef struct{
QElemType *base;//动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
基操
初始化循环队列
每次在这里我都容易陷入一个陷阱,就是没有SqQueue *Q = (SqQueue *)malloc(sizeof(SqQueue));
这样就会因为没有分配存储空间造成初始化失败。
SqQueue *InitQueue(){
SqQueue *Q = (SqQueue *)malloc(sizeof(SqQueue));
Q->base = (QElemType *)malloc(sizeof(QElemType));//分配存储空间
if(!Q->base){
exit(-2);
}
Q->front = Q->rear = 0;
return Q;
}
入队列
这里判断队列是否为空我刚开始很难理解。
为什么要对MAXQSIZE取余??
⭐⭐⭐⭐⭐
我的理解:
需要注意一下,虽然说是循环队列,但是可以看到,关于队列的定义并没有介绍到任何有关循环的概念。
那么循环队列如何循环??
核心就在 %MAXQSIZZE里。
前面已经说到,普通顺序队列的局限就在于有可能队列前面是空的,但是队列已经插入不了元素了。
其实循环队列也有这个局限。插入元素的位置也会不断增大,一直到超过队列的最大空间。
但是%MAXQSIZE 后,如果插入的位置大于队列的最大空间,取余后就会从队列头重新插入
举一个例子:
队列的最大长度是10,当插入第10个元素时,(循环队列中有一个位置是空的,用来区分栈空还是栈满)
此时已经超过了栈的最大空间,如果第0,1个位置是空的。
用10%10 = 0;该元素就会从第0个位置插入,实现了循环队列的效果
说的不是很清楚,有不对的地方希望大家可以指出来
SqQueue *EnQueue(SqQueue *Q,QElemType e){
if((Q->rear+1)%MAXQSIZE==Q->front){
printf("队列已满\n");
}
Q->base[Q->rear] = e;
Q->rear = (Q->rear+1)%MAXQSIZE;
printf("%d入队列\n",e);
return Q;
}
出队列
SqQueue *DeQueue(SqQueue *Q){
QElemType e;
if(Q->front == Q->rear){
printf("栈为空\n");
exit(-2);
}
e = Q->base[Q->front];
Q->front = (Q->front+1)%MAXQSIZE;
printf("%d出队列\n",e);
return Q;
}
完整程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXQSIZE 100//最大队列长度
#define ERROR 0;
#define OK 1;
#define OVERFLOW -2;
typedef int QElemType;
typedef struct{
QElemType *base;//动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
SqQueue *InitQueue(){
SqQueue *Q = (SqQueue *)malloc(sizeof(SqQueue));
Q->base = (QElemType *)malloc(sizeof(QElemType));//分配存储空间
if(!Q->base){
exit(-2);
}
Q->front = Q->rear = 0;
return Q;
}
SqQueue *EnQueue(SqQueue *Q,QElemType e){
if((Q->rear+1)%MAXQSIZE==Q->front){
printf("队列已满\n");
}
Q->base[Q->rear] = e;
Q->rear = (Q->rear+1)%MAXQSIZE;
printf("%d入队列\n",e);
return Q;
}
SqQueue *DeQueue(SqQueue *Q){
QElemType e;
if(Q->front == Q->rear){
printf("栈为空\n");
exit(-2);
}
e = Q->base[Q->front];
Q->front = (Q->front+1)%MAXQSIZE;
printf("%d出队列\n",e);
return Q;
}
int QueueLength(SqQueue *Q){
int length = (Q->rear - Q->front + MAXQSIZE) % MAXQSIZE;
return length;
}
int main(){
/*初始化队列*/
SqQueue *Q = InitQueue();
/*入队列*/
Q = EnQueue(Q,1);
Q = EnQueue(Q,2);
Q = EnQueue(Q,3);
Q = EnQueue(Q,4);
/*求队列长度*/
printf("当前队列长度为%d\n",QueueLength(Q));
/*出队列*/
Q = DeQueue(Q);
Q = DeQueue(Q);
Q = DeQueue(Q);
Q = DeQueue(Q);
Q = DeQueue(Q);
return 0;
}