文章首发于 2020-10-15 知乎文章:数据结构(C语言)-循环队列基本操作
作者:落雨湿红尘(也是我o)
导语
队列是一种先进先出(first in first out,FIFO)的线性表,是一种常用的数据结构。
它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
图1 队列
队列有很多种,按照存储结构划分,有链式队列,循环队列,单向队列,双端队列。实现队列的方式也有很多种,如基于链式存储结构的链接队列(又称链队列),基于顺序存储结构的队列。
本文介绍基于数组(一种顺序存储结构)的循环队列的实现和一些基本操作,并用代码的形式讲解
一些概念
队列的顺序存储结构和顺序栈类似
在队列的顺序存储结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外,还需要设置头尾两个指针front和rear,分别指示队列头元素及队尾元素的位置
我们规定
- 初始化建立空队列时,令front=rear=0
- 每当插入新的队尾元素时,“尾指针增1”
- 每当删除队头元素时,“头指针增1”
- 在非空队列中,头指针始终指向队列头元素,尾指针始终指向队列尾元素的下一个位置
图2 队列的顺序存储结构
在入队和出队的操作中,头尾指针只增加不减小,致使被删除元素的空间永远无法重新利用,因此,尽管队列中实际的元素个数远远小于向量空间的规模,但也可能由于尾指针巳超出向量空间的上界而不能做入队操作,该现象称为假溢出
解决办法:将顺序队列臆造为一个环状的空间,称之为循环队列
循环队列
循环队列的结构如下图所示
图2 循环队列
以下代码,实现了一个可以保存学生学号(最长12位的字符串)循环队列,基于数组这种顺序存储结构,,实现了它的初始化,出队,入队,队列销毁的操作
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define OK 1
#define NO 0
#define MAXSIZE 100
//循环队列结构
typedef struct LoopQueue{
char* base[MAXSIZE];//数据域由一个char*数组构成
int front;//队头索引,指向队列第一个数据所在位置
int rear; //队尾索引,指向队列最后一个数据后一个位置
}LoopQueue;
//循环队列初始化。该循环队列基于数组,因此一旦声明一个LoopQueue变量
//其内部的base数组空间便自动分配,不需要自己分配
//只需初始化队头和队尾索引
int initLQueue(LoopQueue *Q){
Q->front = Q->rear =0;
return OK;
}
//返回长度
int getLenth(LoopQueue *Q) {
return (Q->rear - Q->front + MAXSIZE)%MAXSIZE;
}
//插入元素
int insertLQueue(LoopQueue *Q){
//这里牺牲掉了一个储存位置,用rear+1来和队头索引相比较以判断是否为满,
//是为了和队列判空条件相区分
//判断队列是否 满,如果已满,返回NO
if((Q->rear+1)%MAXSIZE == Q->front) return NO;
char * stuId = (char*)malloc(sizeof(12));
scanf("%s",stuId) ;
Q->base[Q->rear] = stuId;
Q->rear = (Q->rear+1)%MAXSIZE;
return OK;
}
//元素出队
char* outLQueue(LoopQueue *Q){
//判断队列是否为空
if(Q->rear ==Q->front) return NULL;
char * data_return = Q->base[Q->front];
Q->front = (Q->front+1)%MAXSIZE;
return data_return;
}
//销毁队列,也是由于该循环队列基于数组,不需要分配内存
//只需重置队头和队尾索引即可
int destroyLQueue(LoopQueue *Q){
Q->front = Q->rear=0;
return OK;
}
//打印队列
void displayLQueue(LoopQueue *Q) {
if(Q->rear ==Q->front) printf("队列为空\n");
//只要队列不为空 ,就从队头开始打印
int p = Q->front,num=1;
while(p !=Q->rear){
printf("第%d个学生学号:%s \n",num++,Q->base[p]);
p = (p+1)%MAXSIZE;
}
}
int main(){
// 初始化队列
LoopQueue Q;
//定义一个选择变量
int choice;
//元素插入个数
int num;
//出队返回值
char* back;
do{
printf("===========循环队列操作==========\n");
printf("1.初始化循环队列\n");
printf("2.元素入队\n");
printf("3.元素出队\n");
printf("4.销毁队列\n");
printf("5.打印队列\n");
printf("0.退出\n");
printf("=====================\n");
printf("请输入您的选择:");
scanf("%d",&choice);
switch(choice){
case 1:
initLQueue(&Q)?printf("------------\n -->> 循环队列初始化成功\n------------\n"):printf("------------\n -->> 循环队列初始化失败\n------------\n");
break;
case 2:
printf("请输入插入元素个数:");
scanf("%d",&num);
int i;
for(i=0;i<num;i++){
printf(" 请输入第%d个学生的学号:",i+1);
if(!insertLQueue(&Q))printf("------------\n-->> 第%d元素入队失败\n",i+1);
}
break;
case 3:
back = outLQueue(&Q);
back? printf("------------\n 元素: %s 出队,还剩%d个元素\n",back,getLenth(&Q)):printf("------------\n队列为空,无法出队!\n");
free(back); //回收内存,防止内存泄露(感谢评论区朋友提醒)
break;
case 4:
destroyLQueue(&Q)?printf("------------\n队列销毁成功!\n"):printf("------------\n队列销毁失败!\n");
break;
case 5:
displayLQueue(&Q);
break;
case 0:
printf("\n-->> 退出\n");
exit(0);
break;
default:
break;
}
}while(choice);
}
以上代码经过调试,我自认为没有问题(鄙人才疏学浅,欢迎指正)
如果读者朋友们有疑问和更正,欢迎评论区补充和探讨