队列Queue(c语言实现)

前言

队列是数据结构中内存存储数据的一种方式,他的特点是先进先出,固定一端进和出。

实现思路

一般队列的实现是用链式结构,因为链式结构使得队列在出队列和容量上显得非常便利。所以,一般都是用链表的结构。和其他的数据结构大致方向一样,都是建立三个文件,Queue.h(头文件引用和#define等等)、Queue.c(函数的书写)、test.(测试队列)。

队列的函数主要分为:初始化和销毁、队尾入队列和队头出队列、获取队列尾部元素和获取队列头部元素、获取队列有效数据个数、检测队列是否为空

具体思路

Queue.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
//队的结点 链式节点
typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;
//初始化队列
void QueueInit(Queue* pq);
//销毁队列
void QueueDestroy(Queue* pq);
//队尾入队列
void QueuePush(Queue* pq, QDataType x);
//队头出队列
void QueuePop(Queue* pq);
//获取队列头部yuansu
QDataType QueueFront(Queue* pq);
//获取队列尾部元素
QDataType QueueBack(Queue* pq);
//获取队列中有效数据个数
int QueueSize(Queue* pq);
//检测队列是否为空(检空)
bool QueueEmpty(Queue* pq);

队列的创建不同于顺序表和链表,他需要两个结构体,这是有他的功能实现层面要求的。如果你只有一个结构体,那你这个结构体既有全局信息的又有局部信息,这就很臃肿,不符合功能单一这一原则,并前维护起来也很复杂。但如果你创建了两个结构体来维护队列,结构清晰,逻辑严谨,维护起来就很简单,全局信息和局部信息分起来维护,责任清晰,函数实现起来也很简便。

Queue.c

初始化和销毁

初始化

void QueueInit(Queue* pq)
{
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

和链表一样,只不过我们这里把它包装成了一个 函数,对头指针和尾指针指置空就好了

销毁

​
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		Queue* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

​

这里的销毁链表一样,一个一个释放。

队尾入队列和队头出队列

队尾入队列

void QueuePush(Queue* pq, QDataType x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("Malloc Failed\n");
		return;
	}
	newnode->next = NULL;
	newnode->data = x;
	if (pq->head == NULL)
	{
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

这里其实就是链表的尾插,但不过我们这里有了尾指针,可以直接插入,不用在遍历一遍了,大大提高了效率。情况的分析也是和链表尾插差不多。

情况一:

头指针就是NULL,所以直接把新的节点赋值给头指针和尾指针就行了。这里需要做一下检查,断言一下头指针为NULL的情况下尾指针是否为为NULL。虽然正常情况是尾指针和头指针一样都为NULL,但这里我们以防万一,防止别人篡改。

情况二:

头指针不为空,如果是链表这里还需要遍历,但我们这里有了尾指针就可以直接插入。

最后不要忘记,size++。

队头出队列

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

这里其实就是链表的头删,但这里不同的是。如果我们按照链表的尾插就会出现野指针的情况。所以,我们还需要做一下处理。

处理方式一:

在原先的链表尾插的代码基础上加一个if语句

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	QNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	if (pq->head == NULL)
	{
		pq->tail = NULL;
	}
	pq->size--;
}
处理方式二:

直接分类处理,判断头指针的next是否NULL,如果为空,直接释放头指针再把尾指针和头指针置空。如果不为空,按照链表的方式处理。我的代码就是这种处理方式。

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

最后不要忘记,size--。

获取队列尾部元素和获取队列头部元素

按照队列的特点,先进先出,不应该出现尾部元素先出的函数。但是,很多地方的时候都会用到这个函数,很多题目也会用到。所以,我们不妨创建一个。这两个函数都有一个共性,就是检空。我们在获取队列的数据时要检查一下队列是否由数据,防止野指针问题出现。

获取队列尾部元素

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

这里尾指针的作用体现的就是淋漓尽致了,不用再像链表一样遍历,直接尾插就欧克了。

获取队列头部元素

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

这里就是加简单的return头指针的data,So Easy!

获取队列有效数据个数

int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

这里就体现了我们为什么要定义size了,就是方便获取队列长度,不用像链表那样,还要一个一个遍历。

检测队列是否为空

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

使用bool类型,再进行等号判断,返回值

代码

#include "Queue.h"
void QueueInit(Queue* pq)
{
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("Malloc Failed\n");
		return;
	}
	newnode->next = NULL;
	newnode->data = x;
	if (pq->head == NULL)
	{
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	/*QNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	if (pq->head == NULL)
	{
		pq->tail = NULL;
	}*/
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

总结

队列的实现和链表的实现很相似,但是不同之处是,他在继承了链表的优点之后又克服了链表的很多缺点,不如在求长度时就很方便,不用再一个一个遍历。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值