目录
上节我们讲了栈的典型应用,即深度优先算法,本节开始学习线性结构中的最后一种形式:队列!
我们说过栈的特点是“先进后出”,那么本节所讲的队列的特点是“先进先出”。
栈有两种形式,一种是顺序栈,另一种是链栈;同样,队列也有两种形式,一种是顺序队列,另一种是链式队列。
我们只讲顺序队列,因为链式队列和链表近似,就不再讲了。
顺序队列的概述
和栈的栈底和栈顶类似,队列有队头和队尾的概念。队头和队尾需要两个指针,但是顺序结构本质是数组,所以我们没有指针,只有下标,因此我们接下来所说的指针其实是指下标。
如果继续往队尾放数据,那队尾指针(下标)就往后挪,这个过程就叫做“进队操作”
如果我们从队头取出来几个数据,那队头指针(下标)就得往后挪,这个过程就叫做“出队操作”
如果这个时候再往队尾放三个数据,队尾指针就往后挪
此时,如果还有数据想要进队就进不来了,因为顺序存储是有容量限制的,5的后面已经越界了。
可以发现此时队列其实还没有满,队头前还有两个空间,这种情况下内存的利用率就非常低了。
如何来解决这个问题?
我们一般使用“循环队列”,这样数据就可以放在队头前的空间了。
此时队尾指针应该指针这个2,队头指针不动
如果4,6,8,9,0,5要出队的话,队头指针也会跟着往后移动
最后5出队的时候,队头就指向了队尾
我们得出一个结论,当队头和队尾重合的时候,这个队列就只剩下一个元素。
那如果这个2再出队的话,则队头就会指向第二个空间的位置
这样我们就不知道这是一个什么样的队列。
于是我们就再规定:让队尾指向最后一个元素的后一个位置,比如:
这样又会带来一个问题:这个队列的容量是8,但是只能存放7个元素
如果这个时候5出队了,则队头指针就指向2
如果2也出队了,队头就指向队尾的位置
也就是说当队头和队尾重合的时候,此时队列是个空队。换句话说,空队的条件:front==rear;
如果此时有个元素进队,则这个元素就放在rear指向的位置,同时rear往后挪一个位置
计算队列的长度
比如这样一个队列:
此时元素2的下标(即front)是2,rear所指向的位置下标是4
我们直接用下标4-2=2,则长度就是2,但是换一种情况的话,这种计算方式就有问题了
比如这样一个队列:
此时rear指向的位置下标是0,我们就不能直接用rear-front的下标来计算长度了
所以求长度的正确做法是:(rear-front+SIZE)%SIZE
用上面的队列验证一下,(0-2+8)%8=6,最终长度就是6
队满的条件
队满的条件是:rear和front相邻,用公式表达:(rear+1)%SIZE=front
比如下面这种情况:(7+1)%8 =0,所以这种情况也属于rear和front相邻的情况
再如下面这种情况也满足队满条件:
我们也是用结构体来表示队列
Struct Queue
{
int*data; //这块内存存放数据
int front;
int rear;
};
下面开始写代码
创建一个队列文件夹
定义队列
Queue.h
队列的初始化
Queue.c
在Queue.h中声明一下这个函数
Main.c
运行结果:
进队操作
初始化之后,此时front和rear都指向这块内存的第一个空间。进队操作操作的是队尾,类似于现实生活中的排队,如果要人要进队,肯定是从队尾排起。也就是把要进去的数放在下标为rear的地址里面,然后让rear往后挪一个。
Queue.c
在Queue.h中声明一下这个函数
Main.c
运行结果
容量是10,最多只能进9个,所以最后一个进队失败
判断队列是否为空
Queue.c
在Queue.h中声明一下这个函数
Main.c
运行结果
获取队头元素
Queue.c
在Queue.h中声明一下这个函数
Main.c
运行结果
出队操作
根据队列的特点:先进先出,所以出队操作的是队头元素,返回出队的元素,然后front往后走一个
Queue.c
在Queue.h中声明一下这个函数
Main.c
运行结果
队列的清空操作
直接让front和rear相等就是空队
Queue.c
在Queue.h中声明一下这个函数
Main.c
运行结果
清空之后就获取不到队头元素了,可以验证一下
运行结果
栈和队列都不存在遍历操作,因为栈只能看到栈顶,队列只能看到队头和队尾,中间的元素是看不到的
队列的销毁操作
销毁操作的主要目的就是将我们初始化时申请给data的内存释放掉
Queue.c
在Queue.h中声明一下这个函数
Main.c
运行结果
销毁之后就不能进队了,可以验证一下
完整代码
Queue.c
#include "queue.h"
#include <stdlib.h>
//队列的初始化
int InitQueue(seqQueue *q)
{
if(NULL==q)
return FAILURE;
q->data=(T*)malloc(sizeof(T)*SIZE);
if(NULL==q->data)
{
return FAILURE;
}
q->rear=q->front=0;
return SUCCESS;
}
//进队操作
int PushQueue(seqQueue *q,T num)
{
if(NULL==q||NULL==q->data)
return FAILURE;
if((q->rear+1)%SIZE==q->front)//如果队满了
return FAILURE;
q->data[q->rear]=num;//将数据放在下标为rear的位置
q->rear=(q->rear+1)%SIZE;//rear往后挪一个
return SUCCESS;
}
//判断队列是否为空
int EmptyQueue(seqQueue q)
{
return (q.front==q.rear)? SUCCESS:FAILURE;
}
//获取队头元素
T GetTop(seqQueue q)
{
return ((q.front==q.rear)? FAILURE: q.data[q.front]);
}
//出队操作
T PopQueue(seqQueue *q)
{
T num;
if(NULL==q)
return FAILURE;
if(q->front==q->rear)
return FAILURE;
num=q->data[q->front];
q->front=(q->front+1)%SIZE;//front不能直接++,防止数组越界
return num;
}
//清空操作
int ClearQueue(seqQueue*q)
{
if(NULL==q)
return FAILURE;
q->front=q->rear=0;//只要相等就可以,不管等于多少
return SUCCESS;
}
//销毁操作
int DestroyQueue(seqQueue*q)
{
if(NULL==q)
return FAILURE;
if(q->data)//如果不为空就释放
free(q->data);
q->data=NULL;//置为空指针
return SUCCESS;
}
Queue.h
#ifndef _QUEUE_H
#define _QUEUE_H
#define SIZE 10
#define SUCCESS 1000
#define FAILURE 1001
//为了方便修改结构体成员的数据类型,所以将数据类型重命名
typedef int T;
//定义队列
typedef struct seqQueue
{
T *data;
int rear;
int front;
}seqQueue;
int InitQueue(seqQueue *q);
int PushQueue(seqQueue *q,T num);
int EmptyQueue(seqQueue q);
T GetTop(seqQueue q);
T PopQueue(seqQueue *q);
int ClearQueue(seqQueue*q);
int DestroyQueue(seqQueue*q);
#endif
Main.c
#include "queue.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main()
{
//创建队列
seqQueue queue;
//初始化队列
int ret=InitQueue(&queue);
if(SUCCESS==ret)
{
printf("队列初始化成功\n");
}
else
{
printf("队列初始化失败\n");
}
//进队操作
srand(time(NULL));
int i,num;
for(i=0;i<10;i++)//进队10个
{
num=rand()%20;
ret=PushQueue(&queue,num);
if(SUCCESS==ret)
{
printf("%d 进队成功\n",num);
}
else
{
printf("%d 进队失败\n",num);
}
}
//判断队列是否为空
ret=EmptyQueue(queue);
if(SUCCESS==ret)
{
printf("队列为空\n");
}
else
{
printf("队列不为空\n");
}
//获取对头元素
ret=GetTop(queue);
if(FAILURE==ret)
{
printf("没有队头元素\n");
}
else
{
printf("队头元素是%d\n",ret);
}
//出队操作
for(i=0;i<5;i++)//5个出队
{
ret=PopQueue(&queue);
if(FAILURE==ret)
{
printf("出队失败\n");
}
else
{
printf("%d 出队成功\n",ret);
}
}
//清空操作
ret=ClearQueue(&queue);
if(SUCCESS==ret)
{
printf("清空成功\n");
}
else
{
printf("清空失败\n");
}
//获取对头元素,验证是否清空成功
ret=GetTop(queue);
if(FAILURE==ret)
{
printf("没有队头元素\n");
}
else
{
printf("队头元素是%d\n",ret);
}
//销毁操作
ret=DestroyQueue(&queue);
if(SUCCESS==ret)
{
printf("销毁成功\n");
}
else
{
printf("销毁失败\n");
}
//进队操作,验证是否销毁成功
for(i=0;i<10;i++)//进队10个
{
num=rand()%20;
ret=PushQueue(&queue,num);
if(SUCCESS==ret)
{
printf("%d 进队成功\n",num);
}
else
{
printf("%d 进队失败\n",num);
}
}
return 0;
}
下节开始讲广度优先算法!
QQ交流群:963138186
本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓