嵌入式全栈开发学习笔记---数据结构(队列)

目录

顺序队列的概述

计算队列的长度

队满的条件

定义队列

队列的初始化

进队操作

判断队列是否为空

获取队头元素

出队操作

队列的清空操作

队列的销毁操作

完整代码

Queue.c

Queue.h

Main.c


上节我们讲了栈的典型应用,即深度优先算法,本节开始学习线性结构中的最后一种形式:队列!

我们说过栈的特点是“先进后出”,那么本节所讲的队列的特点是“先进先出”。

栈有两种形式,一种是顺序栈,另一种是链栈;同样,队列也有两种形式,一种是顺序队列,另一种是链式队列。

我们只讲顺序队列,因为链式队列和链表近似,就不再讲了。

顺序队列的概述

和栈的栈底和栈顶类似,队列有队头和队尾的概念。队头和队尾需要两个指针,但是顺序结构本质是数组,所以我们没有指针,只有下标,因此我们接下来所说的指针其实是指下标。

如果继续往队尾放数据,那队尾指针(下标)就往后挪,这个过程就叫做“进队操作”

如果我们从队头取出来几个数据,那队头指针(下标)就得往后挪,这个过程就叫做“出队操作”

如果这个时候再往队尾放三个数据,队尾指针就往后挪

此时,如果还有数据想要进队就进不来了,因为顺序存储是有容量限制的,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

本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vera工程师养成记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值