作者: BooleanChar12
✨博客主页: BooleanChar12的博客
很喜欢的一句话: Because we all stand on the shoulders of giants.
如有bug和疑惑欢迎大家与我
觉得博主文章写的不错的话,希望大家三连(关注,点赞,评论),多多支持一下!!
--------------------------------------------------------------------------==专栏回顾==-------------------------------------------------------------------------------
☕Java(从入门到放弃) 数据结构(从放弃到入门) Android开发(从入门到入土) 计算机组成原理(从1到0) 计算机网络(从上网到退网) 操作系统(从0到0.1) C语言练习题 Leetcode(狂刷笔记) 微信小程序开发日记
写在前面:因初学数据结构,读者如发现有错误或者疑惑,可评论私信,一起交流,如觉得写得不错,还请点赞,谢谢!
1.为什么front和rear是int类型的变量却还要叫指针?
个人看法:是因为顺序循环队列指向的是一块连续的内存空间(可以看作一个数组空间),
在操作队列的时候只存在地址加一位或者减一位的情况,
使用指针来表示在访问的时候反而更加麻烦,不如用数组的下标(就是这里定义的int front表示的其实是base[]数组的下标)来表示
当然也可以用指针来表示,下面相应的代码也要改变,只是对于操作这种连续且每次只会+1,-1的地址来说
用这种方式访问更加方便2.如何判断循环队列是否满了?
由于顺序循环队列的队空和队满的条件都是Q.frontQ.rear,就无法判断到底是队空还是队满,我们就得重新想一个判断条件
解决方法:1.设计一个变量记录队中元素个数,个数为0则队空,个数为MAXQSIZE则队满
2.设计一个标志以区别队空和队满
3.少用一个队列格子,即用(Q.rear+1)%MAXQSIZEQ.front来判断,即下一个rear所在的位置与对头front所在位置相等
注:下面代码采用的是第三种方式判断队空或者队满3.浅谈为什么要用到%运算?
个人看法:1.%取模运算的本质是让运算后的结果落入0~这个模数中,
其实就是循环减去一个数直到被模的数小于模数(即被模的数不够减去1*模数的时候循环终止),然后将被模的数返回作为模运算的结果
为什么这么说呢,举例:20%6=21.循环开始:第一次 20-6=14 2. 第二次 14-6=8 3. 第三次 8-6=2 4.循环终止:第四次 2-6发现减不过,终止循环,把2返回作为本次模运算的结果 再比如:1%6=1 2%6=2 3%6=3 4%6=4 5%6=5 6%6=0 7%6=1 8%6=2 9%6=3...... 不难看出,无论哪个正整数%6,最后的结果一定都在[0,6]这个区间内, 而且随着正整数的增大,结果是每次+1,并且循环的,即0,1,2,3,4,5,6,0,1,2,3,4,5,6,0...... 所以我们巧妙利用%运算的这个特点,把实际的线性顺序内存可以当作循环顺序内存使用
4.解读Q.rear = (Q.rear+1)%MAXQSIZE
1.如果 Q.rear+1
2.如果 Q.rear+1>MAXQSIZE之后,即发生假溢出,则运算结果就会从进入新的[0,100]的循环,%运算保证每次rear++后的结果落在[0,100]之间
3.下面代码中的 Q.front = (Q.front+1)%MAXQSIZE同理5.为什么队列的长度不是Q.rear-Q.front而是(Q.rear-Q.front+MAXQSIZE)%MAXQSIZE?
1.普通队列的长度:length = Q.rear-Q.front 2.循环队列的长度: (Q.rear-Q.front+MAXQSIZE)%MAXQSIZE 3.个人认为,(Q.rear-Q.front+MAXQSIZE)%MAXQSIZE其实就是这个if语句的整合: if((Q.rear-Q.front)>0) { printf("当前队列的长度:%d\n",Q.rear-Q.front); } else { printf("当前队列的长度:%d\n",Q.rear+MAXQSIZE-Q.front); } 3.1如果Q.rear>Q.front,那队列长度就是Q.rear-Q.front 3.2如果Q.rear
Q.front, 因为队列不满,Q.rear-Q.front的值一定小于1*MAXQSIZE, 所以(Q.rear+MAXQSIZE-Q.front)的值一定大于1*MAXQSIZE,小于2*MAXQSIZE, 所以(Q.rear+MAXQSIZE-Q.front)%MAXQSIZE的最终结果还是 Q.rear-Q.front eg:假设MAXQSIZE=6,某一状态时Q.rear=4,Q.front=2;(即队列入队4个元素后,出队2个元素,此时队长应为2) 0<4-2<1*6 0<4-2+6<2*6 取模之后相当于把加的6给减去了,剩下的就是计算结果,即 4-2就是计算结果,也就是4-2就是队长 4.2如果Q.rear
# include
# include //使用malloc()函数必须引入的库
#define MAXQSIZE 5 //定义Q队列的最大长度,相当于一个数组arr[100]就是arr[MAXSIZE]
#define OK 1 //定义返回状态码 return OK就是return 1 在函数正确执行完的时候用
#define ERROR 0 //定义返回状态码 return ERROR就是return 0
#define OVERFLOW -2 //定义返回状态码 return OVERFLOW就是return -2 在内存溢出的时候用
typedef int QElemType;//定义下面代码中出现的QElemType就是表示int
typedef int Status; //定义下面代码中出现的Status就是表示int
//队列结构体的定义
typedef struct {
QElemType * base;//指向动态分配内存的首地址
int front;//头指针
int rear;//尾指针
} SqQueue;
//1.队列初始化
Status InitQueue(SqQueue &Q) {
Q.base = (QElemType *)malloc(MAXQSIZE*sizeof(QElemType));//动态创建连续的内存空间并将此内存空间的首地址赋给Q队列的base指针,即Q队列的base指针指向了这块连续的内存空间
if(!Q.base) { //判断能不能在内存中找到这样一块连续空间,没有找到就exit(OVERFLOW),即返回状态OVERFLOW
printf("内存溢出,无法开辟空间!");
return OVERFLOW;
}
//如果找到了,就让Q队列的头指针和尾指针都等于0,即指向base[]数组的0的位置,即那块内存空间的首地址
Q.front=Q.rear=0;
printf("初始化队列成功!\n");
//返回状态OK
return OK;
}
//2.入队
Status EnQueue(SqQueue &Q) {
//入队的时候首先需要判断队列是否满了
if((Q.rear+1)%MAXQSIZE==Q.front) {
printf("当前队满!\n");
return OVERFLOW;
}
int input;//定义input变量接收用户输入的入队元素,用int声明的变量的空间是由系统自动分配的,当然这里也可以使用malloc动态创建内存空间
printf("请输入入队元素:\n");
scanf("%d",&input);
Q.base[Q.rear] = input;//从队尾入队,把输入的数据放到Q队列的base数组的下标为Q.rear的位置
Q.rear = (Q.rear+1)%MAXQSIZE;//rear++,这里的++在循环队列中要用(Q.rear+1)%MAXQSIZE,来解决假溢出的问题
return OK;
}
//3.出队
Status DeQueue(SqQueue &Q) {
//出队的时候首先判断队列是否为空 当然这里可以直接使用已经写好的判断队空的QueueEmpty()函数
if(Q.front==Q.rear) {
printf("当前队空,请先入队!\n");
return ERROR;
}
int output;
output = Q.base[Q.front];//从队头入队,记录出队数据
//front++,同样这里的++在循环队列中要用(Q.front+1)%MAXQSIZE,来解决假溢出的问题
Q.front = (Q.front+1)%MAXQSIZE;
//打印出队元素
printf("出队元素为:%d\n",output);
return OK;
}
//4.取队头元素
Status GetHead(SqQueue &Q) {
//队列不空的时候才能取队头元素 当然这里可以直接使用已经写好的判断队空的QueueEmpty()函数
if(Q.front==Q.rear) {
printf("当前队空,请先入队!\n");
return ERROR;
}
int head;
head = Q.base[Q.front];//将当前队头元素的值赋给head变量
printf("当前队头元素为:%d\n",head);
return OK;
}
//5.判断队列是否为空
Status QueueEmpty(SqQueue &Q) {
//直接把上面出队时的判空逻辑复制过来,因为判断条件都是一样的
if(Q.front==Q.rear) {
printf("当前队空,请先入队!\n");
return ERROR;
}
printf("当前队列不为空\n");//否则当前队列不为空
}
//6.遍历队列中的元素TravelQueue
Status TravelQueue(SqQueue &Q) {
//先判断队列是否为空 当然这里可以直接使用已经写好的判断队空的QueueEmpty()函数
if(Q.front==Q.rear) {
printf("当前队空,请先入队!\n");
return ERROR;
}
//为防止遍历一次就把Q中的rear或者front的值修改,所以这里新建一个变量来接受rear或front的值
//当然不想重新定义一个变量来保存front的值的话,可以在该函数的参数部分改为(SqQueue Q),
//但是这样相当于在内存中重新拷贝了一份Q,而且每次遍历都会这样拷贝一份,内存开销大,速度和效率都会降低
int front2 = Q.front;
printf("当前队列中的元素为:");
while(!(front2==Q.rear)) {
printf("%d ",Q.base[front2]);//输出当前base[front2]所表示的元素
front2=(front2+1)%MAXQSIZE;//让front2表示下一次所在的位置
}
printf("\n");
return OK;
}
//7.求当前队列长度
Status QueueLength(SqQueue &Q) {
//printf("当前队列的长度:%d\n",(Q.rear-Q.front+MAXQSIZE)%MAXQSIZE);
return OK;
}
//8.销毁队列DestroyQueue
Status DestoryQueue(SqQueue &Q) {
//让front和rear相等即可
Q.front=Q.rear;
printf("队列销毁成功!");
return OK;
}
int main(void) {
SqQueue Q;//创建一个队列结构体Q
int option;//变量option用于判断用户选择的栈操作
int loop=1;//变量loop表示循环的条件
do {
//菜单
printf("1.初始化队列\n");
printf("2.判断当前队列是否为空\n");
printf("3.入队\n");
printf("4.出队\n");
printf("5.取队头元素\n");
printf("6.获取当前队列长度\n");
printf("7.遍历队列中元素\n");
printf("8.销毁队列\n");
printf("9.退出系统\n");
printf("请输入你的操作:");
scanf("%d",&option);
switch(option) {
case 1://初始化队列
InitQueue(Q);//调用初始化队列的函数,传入Q这个队列
break;
case 2://判断当前队列是否为空
QueueEmpty(Q);
break;
case 3://入队
EnQueue(Q);
break;
case 4://出队
DeQueue(Q);
break;
case 5://取队头元素
GetHead(Q);
break;
case 6://获取当前队列长度
QueueLength(Q);
break;
case 7://遍历队列中元素
TravelQueue(Q);
break;
case 8://销毁队列
DestoryQueue(Q);
break;
case 9://退出系统
loop = 0;
printf("你退出了系统......");
}
printf("\n\n"); //每次执行完换两行,对用户视觉友好
} while(loop);//循环条件为loop
}