C语言实现顺序队列、循环队列和链式队列
文章目录
一.顺序队列
顺序队列的实现比较简单,使用数组的方式存储即可。由于这种方式是静态存储,即一旦规定内存大小便不可改变,所以有可能会发生溢出,因而在实际使用中也没有太大的价值。所以我这里只做了一个简单实现,实现基本的入队和出队以及判断队空和队满等功能。
顺序队列的定义
#define MaxSize 5 //顺序队列采用顺序存储,手动规定最大容量
typedef int ElemType;
//------ 结构体定义 -------//
typedef struct SqlQueue
{
ElemType data[MaxSize];//用数组存放数据
int front,rear;//队头和队尾指针
}SqlQ;
初始状态(队空条件):Q->front == Q->rear == 0;
入队:队不满时,先送值到队尾元素,再将队尾指针 +1;
出队:队不空时,先取队头元素值,再将队头指针 +1;
(此处参考王道考研课本)
顺序队列全部代码
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h> //根据C99标准,C语言使用bool类型需要添加这个头文件
#define MaxSize 5 //顺序队列采用顺序存储,手动规定最大容量
typedef int ElemType;
//------ 结构体定义 -------//
typedef struct SqlQueue
{
ElemType data[MaxSize];//用数组存放数据
int front,rear;//队头和队尾指针
}SqlQ;
//------ 结构体定义 -------//
//--------- 函数声明 ---------//
void MainMenu();//主菜单,用于显示
void InitQueue(SqlQ *Q);//队列的初始化
bool EnQueue(SqlQ *Q, ElemType e); //入队
bool DeQueue(SqlQ *Q, ElemType *x);//出队
bool GetFront(SqlQ *Q, ElemType *head);//获取队头元素
//--------- 函数声明 ---------//
int main()
{
SqlQ Q;//声明一个队列
InitQueue(&Q);//初始化
ElemType element;
int ch;
ElemType x;//获得出队的元素,方便返回
ElemType head;//获取队头元素
while(1)
{
MainMenu();
printf("请输入您要执行的操作:");
scanf("%d",&ch);
switch(ch)
{
case 0: printf("感谢使用,已退出!"); exit(0); break;
case 1: printf("请输入您要入队的元素:\n");
scanf("%d",&element);
if(EnQueue(&Q,element))
printf("入队成功!\n");
else
printf("入队失败!队已满!\n");
break;
case 2:
if(DeQueue(&Q,&x))
printf("出队成功!出队的元素为:%d\n",x);
else
printf("出队失败!队已空!\n");
break;
case 3:
if(GetFront(&Q,&head))
printf("获取队头元素成功!当前队头元素为:%d\n",head);
else
printf("获取队头元素失败!队已空!\n");
break;
default: printf("您输入的操作指令有误!请重新输入!");
}
}
return 0;
}
//主菜单,显示
void MainMenu()
{
printf("\n\n\n");
printf("\t *************顺序队列的实现************\n\n");
printf("\t ------- 0.退出 \n\n");
printf("\t ------- 1.将数据入队\n\n");
printf("\t ------- 2.执行一次出队操作\n\n");
printf("\t ------- 3.获取当前队头元素\n\n");
printf("\t *************************************\n");
}
void InitQueue(SqlQ *Q)//队列的初始化
{
Q->front = Q->rear = 0;//此时队空,让队头指针指向队头元素,让队尾指针指向队尾元素的下一个位置
}
//入队:队不满时,先将值送到队尾元素,再将队尾指针+1
bool EnQueue(SqlQ *Q, ElemType e)
{
if(Q->rear == MaxSize)
return false;
Q->data[Q->rear] = e;
Q->rear++;
return true;
}
//出队:队不空时,先取队头元素值,再将队头指针+1
bool DeQueue(SqlQ *Q, ElemType *x)
{
if(Q->front == Q->rear )
return false;
*x = Q->data[Q->front];
Q->front++;
return true;
}
//获得队头元素
bool GetFront(SqlQ *Q, ElemType *head)
{
if(Q->front == Q->rear)
return false;
*head = Q->data[Q->front];
return true;
}
测试:
⚠️注意:这种实现方式会有点小瑕疵,即当我一次存满又依次出队之后,此时并不是真正意义上的队空,因为无法再将元素入队。当然也可以采用其他办法,例如修改指针,然后再重新入队,因为这种顺序队列实用性不高,所以只需要知道大概的流程即可。
二.循环队列
循环队列的概念
循环队列是顺序队列的另一种实现方式,只不过是将顺序队列想象成一个环。如下图所示(参考王道考研数据结构课本):
当队首指针Q->front = MaxSize - 1 后,再前进一个位置就自动到0,这可以利用除法取余运算(%)来实现。故有以下情况:
①初始时:Q->front = Q->rear = 0;
②队首指针进一:Q->front = (Q->front+1)%MaxSize ;
③队尾指针进一:Q->rear = (Q->rear+1)%MaxSize
出队入队时,指针都按顺时针方向进一。
则由此得到队空和队满时的条件都是 Q->front == Q->rear .所以为了区分队空和队满,有3种处理办法:
1️⃣牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是一种较为普遍的做法,约定以“队头指针在队尾指针的下一位置作为队满的标志”。
此时有:
队满条件:(Q->rear+1)%MaxSize == Q->front
队空条件:Q->front == Q->rear
队列长度:(Q->rear - Q->front + MaxSize) % MaxSize
2️⃣类型中增设表示元素个数的数据成员。这样,队空的条件为:Q.size == 0; 队满的条件为Q.size == MaxSize. 这两种情况都有Q->front == Q->rear。
3️⃣类型中增设tag数据成员,以区分是队满还是队空。tag等于0时,若因删除导致Q->front == Q->rear,则为队空; tag等于1时,若因插入导致Q->front == Q->rear 则为队满。
这里我选择第2种方法。
相关操作
1.初始化
void InitQueue(CirQ *Q)//队列的初始化
{
Q->front = Q->rear = 0;//此时队空,让队头指针指向队头元素,让队尾指针指向队尾元素的下一个位置
Q->size = 0;//初始时未存元素
}
2.入队
//入队
bool EnQueue(CirQ *Q, ElemType e)
{
//判断队列是否满
if(Q->size==MaxSize)
return false;
Q->data[Q->rear] = e; //把值放入
Q->rear = (Q->rear + 1)% MaxSize;//改变指针
Q->size++;//队列中元素个数+1
return true;
}
3.出队
//出队
bool DeQueue(CirQ *Q, ElemType *x)
{
if(Q->size == 0)//判断队列是否空
return false;
*x = Q->data[Q->front];//获取出队的元素
Q->front = (Q->front + 1)% MaxSize;
Q->size--;//队列中元素个数-1
return true;
}
4.获取队头元素
//获得队头元素
bool GetFront(CirQ *Q, ElemType *head)
{
if(Q->size == 0)//判断队列是否空
return false;
*head = Q->data[Q->front];//获取队头
return true;
}
循环队列全部代码
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h> //根据C99标准,C语言使用bool类型需要添加这个头文件
#define MaxSize 5 //循环队列采用顺序存储,手动规定最大容量
typedef int ElemType;
//------ 结构体定义 -------//
typedef struct CircularQueue
{
ElemType data[MaxSize];//用数组存放数据
int front,rear;//队头和队尾指针
int size;//表示队列中实际存储的个数
}CirQ;
//------ 结构体定义 -------//
//--------- 函数声明 ---------//
void MainMenu();//主菜单,用于显示
void InitQueue(CirQ *Q);//队列的初始化
bool EnQueue(CirQ *Q, ElemType e); //入队
bool DeQueue(CirQ *Q, ElemType *x);//出队
bool GetFront(CirQ *Q, ElemType *head);//获取队头元素
//--------- 函数声明 ---------//
int main()
{
CirQ Q;//声明一个队列
InitQueue(&Q);//初始化
ElemType element;
int ch;
ElemType x;//获得出队的元素,方便返回
ElemType head;//获取队头元素
while(1)
{
MainMenu();
printf("请输入您要执行的操作:");
scanf("%d",&ch);
switch(ch)
{
case 0: printf("感谢使用,已退出!"); exit(0); break;
case 1: printf("请输入您要入队的元素:\n");
scanf("%d",&element);
if(EnQueue(&Q,element))
printf("入队成功!\n");
else
printf("入队失败!队已满!\n");
break;
case 2:
if(DeQueue(&Q,&x))
printf("出队成功!出队的元素为:%d\n",x);
else
printf("出队失败!队已空!\n");
break;
case 3:
if(GetFront(&Q,&head))
printf("获取队头元素成功!当前队头元素为:%d\n",head);
else
printf("获取队头元素失败!队已空!\n");
break;
default: printf("您输入的操作指令有误!请重新输入!");
}
}
return 0;
}
//主菜单,显示
void MainMenu()
{
printf("\n\n\n");
printf("\t *************循环队列的实现************\n\n");
printf("\t ------- 0.退出 \n\n");
printf("\t ------- 1.将数据入队\n\n");
printf("\t ------- 2.执行一次出队操作\n\n");
printf("\t ------- 3.获取当前队头元素\n\n");
printf("\t *************************************\n");
}
void InitQueue(CirQ *Q)//队列的初始化
{
Q->front = Q->rear = 0;//此时队空,让队头指针指向队头元素,让队尾指针指向队尾元素的下一个位置
Q->size = 0;//初始时未存元素
}
//入队
bool EnQueue(CirQ *Q, ElemType e)
{
//判断队列是否满
if(Q->size==MaxSize)
return false;
Q->data[Q->rear] = e; //把值放入
Q->rear = (Q->rear + 1)% MaxSize;//改变指针
Q->size++;//队列中元素个数+1
return true;
}
//出队
bool DeQueue(CirQ *Q, ElemType *x)
{
if(Q->size == 0)//判断队列是否空
return false;
*x = Q->data[Q->front];//获取出队的元素
Q->front = (Q->front + 1)% MaxSize;
Q->size--;//队列中元素个数-1
return true;
}
//获得队头元素
bool GetFront(CirQ *Q, ElemType *head)
{
if(Q->size == 0)//判断队列是否空
return false;
*head = Q->data[Q->front];//获取队头
return true;
}
测试:
三.链式队列(带头结点)
采用链式存储方式的链队列实际上是一个同时带有队头指针和队尾指针的单链表,并且遵循队列只在队头出队和队尾入队的特性。
存储类型定义
typedef struct Node
{
ElemType data;
struct LinkNode *next;//结点的后继指针
}LinkNode;
//链队列
typedef struct
{
LinkNode *front,*rear;//队列的队头和队尾指针
int len;//链队列中元素个数
}LinkQueue;
如上,定义了两个结构体,一个是结点,一个是队列。
入队
//入队
bool EnQueue(LinkQueue *Q, ElemType e)
{
//先申请一个结点
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
s->data = e;//把要入队的元素放入结点
s->next = NULL;//因为在队尾,所以不指向任何元素
//修改队尾指针
Q->rear->next = s;
Q->rear = s; //因为已经规定好队尾指针指向最后一个结点
Q->len++;
return true;
}
入队时,需要先申请一个结点,然后把这个结点插入到队尾。
出队
//出队
bool DeQueue(LinkQueue *Q, ElemType *x)
{
if(Q->len==0)//如果队列中元素个数为0,说明为空队
return false;
LinkNode *p = Q->front->next;//指向头结点的下一个结点
//先获取出队元素的值
*x = p->data;
//再修改指针
Q->front->next = p->next;
if(Q->rear==p)//如果队列中只剩下一个结点,删除之后变空
Q->rear = Q->front;
free(p);
Q->len--;
return true;
}
出队时,在队头删除元素。
链式队列全部代码
由于审美疲劳,我把字体换成了健康的绿色。参考文章:https://blog.csdn.net/qq_31975227/article/details/51758461
#include<stdio.h>
#include <windows.h>
#include<stdbool.h> //根据C99标准,C语言使用bool类型需要添加这个头文件
#include<stdlib.h>
typedef int ElemType;
typedef struct Node
{
ElemType data;
struct LinkNode *next;//结点的后继指针
}LinkNode;
//链队列
typedef struct
{
LinkNode *front,*rear;//队列的队头和队尾指针
int len;//链队列中元素个数
}LinkQueue;
//--------- 函数声明 ----------//
void MainMenu();
void color(short x);
void InitQueue(LinkQueue *Q);//队列的初始化
bool EnQueue(LinkQueue *Q, ElemType e); //入队
bool DeQueue(LinkQueue *Q, ElemType *x);//出队
bool GetFront(LinkQueue *Q, ElemType *head);//获取队头元素
//--------- 函数声明 ----------//
int main()
{
int ch;
ElemType element,head,x;
LinkQueue Q;//创建一条链队列
InitQueue(&Q);
while(1)
{
MainMenu();
printf("请输入您要执行的操作:");
scanf("%d",&ch);
switch(ch)
{
case 0: printf("感谢使用,已退出!"); exit(0); break;
case 1: printf("请输入您要入队的元素:\n");
scanf("%d",&element);
if(EnQueue(&Q,element))
printf("入队成功!\n");
else
printf("入队失败!\n");
break;
case 2:
if(DeQueue(&Q,&x))
printf("出队成功!出队的元素为:%d\n",x);
else
printf("出队失败!队已空!\n");
break;
case 3:
if(GetFront(&Q,&head))
printf("获取队头元素成功!当前队头元素为:%d\n",head);
else
printf("获取队头元素失败!队已空!\n");
break;
default: printf("您输入的操作指令有误!请重新输入!");
}
}
return 0;
}
//主菜单,显示
void MainMenu()
{
color(2);//设置字体为绿色
printf("\n\n\n");
printf("\t *************链式队列的实现************\n\n");
printf("\t ------- 0.退出 \n\n");
printf("\t ------- 1.将数据入队\n\n");
printf("\t ------- 2.执行一次出队操作\n\n");
printf("\t ------- 3.获取当前队头元素\n\n");
printf("\t *************************************\n");
}
void color(short x) //自定义函根据参数改变颜色
{
if(x>=0 && x<=15)//参数在0-15的范围颜色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x); //只有一个参数,改变字体颜色
else//默认的颜色白色
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}
//初始化
void InitQueue(LinkQueue *Q)
{
//申请一个头结点的空间,并且头指针和尾指针都等于它
Q->front = Q->rear = (LinkNode *)malloc(sizeof(LinkNode));
Q->front->next = NULL;//初始时头指针的next为空,因为还没插入元素
Q->len = 0;
}
//入队
bool EnQueue(LinkQueue *Q, ElemType e)
{
//先申请一个结点
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
s->data = e;//把要入队的元素放入结点
s->next = NULL;//因为在队尾,所以不指向任何元素
//修改队尾指针
Q->rear->next = s;
Q->rear = s; //因为已经规定好队尾指针指向最后一个结点
Q->len++;
return true;
}
//出队
bool DeQueue(LinkQueue *Q, ElemType *x)
{
if(Q->front==Q->rear)//如果队列中元素个数为0,说明为空队
return false;
LinkNode *p = Q->front->next;//指向头结点的下一个结点
//先获取出队元素的值
*x = p->data;
//再修改指针
Q->front->next = p->next;
if(Q->rear==p)//如果队列中只剩下一个结点,删除之后变空
Q->rear = Q->front;
free(p);
Q->len--;
return true;
}
//获取队头元素
bool GetFront(LinkQueue *Q, ElemType *head)
{
if(Q->len==0)//如果队列中元素个数为0,说明为空队
return false;
LinkNode *q = Q->front->next;//指向头结点的下一个结点
*head = q->data;
return true;
}
测试:
参考资料
参考王道考研数据结构课本。