详解数据结构之队列、循环队列(源码)

详解数据结构之队列、循环队列(源码)

队列属于线性表

队列:就好比如,我们在排队买东西时排队,第一个先来的第一个买,最后一个到的最后一个买,这里的队列也是满足先进先出,后进后出的规律(First In First Out),允许插入数据的一端叫做队头简称入队列,允许删除数据的一端叫做队尾简称出队列。

队列的存储形式:

  • 顺序存储结构,实现循环队列 ,队列长度时固定的

  • 链式存储结构,实现不循环队列,队列长度理论上是无限大的

两种存储结构实现的队列复杂度:

  • 时间复杂度 O(1)
  • 空间复杂度 O(1)

队列

使用链式结构实现的队列,其基本结构类似于单链表。使用next指针将每一个节点链接。主要区别在于没有使用一个指针来指向节点,这里定义了两个指针指针单链表,一个phead指针指向单链表的头,一个ptail指针指向单链表的尾。

单链表的头为队列的队头,单链表的尾为队列的队尾。

在这里插入图片描述

有了指针指向链表的最后一个节点,就不再需要使用循环遍历到链表的最后一个节点,将入栈的时间复杂度从O(N)优化到O(1)。

typedef int QueueDataType;
typedef struct QueueNode//节点
{
   
	QueueDataType val;
	struct QueueNode* next;

}QueueNode;
struct Queue
{
   
	QueueNode* phead;//指向队头的指针
	QueueNode* ptail;//指向队尾的指针
	int size;//统计单链表的节点个数
};
typedef struct Queue Queue;

功能实现

//初始化
void QueueInit(Queue* p);
//销毁
void QueueDestory(Queue* p);
//入队列,队尾,不就是尾插
void QueuePush(Queue* p, QueueDataType x);
//出队列,队头
void QueuePop(Queue* p);
//取队头数据
QueueDataType QueueFront(Queue* p);
//取队尾数据
QueueDataType QueueBack(Queue* p);
//判空
bool QueueEmpty(Queue* p);
//获取有效数据个数
int QueueSize(Queue* p);

初始化、销毁

初始化

初始化指向队列的两个指针,和对统计节点个数大小的size,由于传递的是地址就不需要返回值。

//初始化
void QueueInit(Queue* p)
{
   
	assert(p);
	p->phead = p->ptail = NULL;
	p->size = 0;
}

销毁

将队列的节点一一销毁,其操作逻辑与单链表相似。

销毁链表,需要对每一个节点进行释放,而只使用一个指针pcur,释放当前的节点就找不到下一个节点了,这里需要使用两个指针。

一个指针用来释放(pcur),一个指针用来指向待释放之后的节点(next)。

当释放完pcur节点后,更新pcur的位置 pcur = next;,用来释放下一个节点,next指针也需要更新用来指向下一个节点 next = pcur->next;,用来对pcur进行更新。直到pcur指向空指针时释放完所有节点,跳出循环。而 phead和ptail指针,还没有置为空,这时候时野指针,最后一步 p->phead = p->ptail = NULL;

void QueueDestory(Queue* p)
{
   
	assert(p);
	QueueNode* pcur = p->phead;
	while (pcur)
	{
   
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	p->phead = p->ptail = NULL;
	p->size = 0;
}

判空

判断队列的节点是否为空并返回一个布尔值。

如上对队列的初始化,两个头尾指针指向的都为空,此时队列就是空,队列为空返回true,不为返回false。

函数起始别忘了对指针p判空,为空则说明队列不存在了。

//判空
bool QueueEmpty(Queue* p)
{
   
	assert(p);
	return p->ptail == NULL && p->phead == NULL;
}

入队列、出队列

入队列

想要进行尾插首先得有一个指针指向最后一个节点,然后将创建的新节点插入到该节点后面,最后更新ptail指针指向的位置
在这里插入图片描述

入队列有两种情况:

  • 队列为空

此时phead和ptail都指向空,插入一个新节点,phead和ptail均指向它。

  • 队列不为空

在ptail指针后插入一个新节点,通过节点的next指针连接,然后更新ptail指针,让其指向新的尾。
在这里插入图片描述

最后别忘了将统计节点个数的size加1

//入队列,队尾,不就是单链表的尾插
void QueuePush(Queue* p, QueueDataType x)
{
   
	assert(p);

	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));//创建新节点
	if (newnode == NULL)//判空,有无开辟成功
	{
   
		perror("malloc");
		return;
	}
	newnode->val = x;//赋值
	newnode->next = NULL;
	
	if (p->phead == NULL)//phead和ptail为空时
	{
   
		p->phead = p->ptail = newnode;
	}
	else//两者不为空
	{
   
		p->ptail->next = newnode;
		p->ptail = newnode;
	}
	p->size++;//别忘了记录新加入的节点
}

出队列

出队列的逻辑并不复杂,前提是指针p不为空,为空则说明队列不存在、队列不为空,为空则说明没有节点可删,接下来就是对空指针解引用。

它同样分为两种情况:

  • 只有一个节点

即phead和ptail指向同一个节点时,将该节点释放,然后将phead和ptail指针指向空。

  • 有许多个节点

一,保存phead之后的节点 QueueNode* Next = phead->next;二,然后释放第一个节点 free(phead);三,最后将 Next 赋给 phead,更新头节点的位置。

最后将size减1
在这里插入图片描述

//出队列,队头,这不就是单链表的头删吗
void QueuePop(Queue* p)
{
   
	assert(p);
	assert(!QueueEmpty(p));
	
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值