初阶数据结构之队列的实现

1 队列的定义

什么是队列呢?队列只允许在一端进行插入数据操作,在另一端进行删除数据操作。队列具有先进先出FIFO(First In First Out)的特性。

队头 :删除数据的一端称为队头。

队尾 :插入数据的一端称为队尾。

2 队列底层结构的选择

底层采用数组来实现,在删除队头元素的时候,需要移动数组元素,时间复杂度为O(N),还会存在空间浪费。


采用链表实现队列,删除队头元素时,只需要让head指向下一个节点同时将头结点的空间还给操作系统。在队尾增加元素时,申请一块空间存放数据,让新节点成为链表尾节点,为了避免在增加元素时需要找链表尾节点,所以创建一个tail指针,指向链表尾节点。

因此采用链表来实现队列再合适不过了。

3 队列的实现

3.1 队列的定义

c 复制代码
typedef int QDataType;

typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QueueNode;

//为了降低时间复杂度,增加一个尾指针,同时为了方便维护代码,对两个指针使用结构体进行封装
typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

3.2 队列的接口

c 复制代码
//初始化队列
void QueueInit(Queue* pq);

//队尾插入数据
void QueuePush(Queue* pq, QDataType x);

//队头出数据
QDataType QueueFront(Queue* pq);

//判断队列是否为空
bool QueueEmpty(Queue* pq);

//删除队头数据
void QueuePop(Queue* pq);

//队列的大小
int QueueSize(Queue* pq);

//队尾出数据
QDataType QueueBack(Queue* pq);

//销毁队列
void QueueDestroy(Queue* pq);

3.2.1 初始化队列

c 复制代码
//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}
复制代码
刚开始队列为空,head和tail指向NULL。保证pq不能为NULL,否则
通过pq去访问head和tail会造成野指针。

3.2.2 队尾插入数据

c 复制代码
//队尾插入数据
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NULL == newnode)
	{
		printf("QueuePush():malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}
复制代码
如果head为NULL,说明队列为空,则让head和tail同时指向头结点。
否则,则让tail->next保存新节点的地址同时让tail指向新节点,使新节
点成为链表的尾节点。

3.2.3 队列是否为空

c 复制代码
//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	if (pq->head == NULL)
	{
		return true;
	}
	else
	{
		return false;
	}
}

head指向NULL,说明队列为空,否则,队列不为空。

3.2.4 队头出数据

c 复制代码
//队头出数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

队列不为空才能出数据,返回队列头结点的数据(因为head指向头结点)。

3.2.5 删除队头数据

c 复制代码
//删除队头数据
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//记录下一个节点的地址
	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	if (pq->head == NULL)
	{
		pq->tail = NULL;
	}
}
复制代码
删除数据首先要保证队列不能为空,其次使head指向头结点的下一个
节点,再释放头结点。如果head指向了NULL,说明队列为空,此时
应该将tail置为NULL。避免野指针的出现。

3.2.6 队列的大小

c 复制代码
//队列的大小
int QueueSize(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	int size = 0;
	while (NULL != cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

对链表进行遍历,计算链表节点的个数。

3.2.7 队尾出数据

c 复制代码
//队尾出数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

保证队列不为空的前提下,返回tail指向的节点的数据。

3.2.8 销毁队列

c 复制代码
//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	while (pq->head!=NULL)
	{
		QueueNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->tail = NULL;
}
复制代码
对链表进行遍历,先记录要删除节点的下一个节点的地址,然后释放
删除节点的空间,让head指向下一个节点。最后,将tail置为NULL
(避免野指针)。

4 队列完整代码的实现

Queue.h

c 复制代码
#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;
}QueueNode;

//为了降低时间复杂度,增加一个尾指针,同时为了方便维护代码,对两个指针使用结构体进行封装
typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

//初始化队列
void QueueInit(Queue* pq);

//队尾插入数据
void QueuePush(Queue* pq, QDataType x);

//队头出数据
QDataType QueueFront(Queue* pq);

//判断队列是否为空
bool QueueEmpty(Queue* pq);

//删除队头数据
void QueuePop(Queue* pq);

//队列的大小
int QueueSize(Queue* pq);

//队尾出数据
QDataType QueueBack(Queue* pq);

//销毁队列
void QueueDestroy(Queue* pq);

Queue.c

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"Queue.h"

//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

//队尾插入数据
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (NULL == newnode)
	{
		printf("QueuePush():malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

//队头出数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

//判断队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	if (pq->head == NULL)
	{
		return true;
	}
	else
	{
		return false;
	}
}

//删除队头数据
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//记录下一个节点的地址
	QueueNode* next = pq->head->next;
	free(pq->head);
	pq->head = next;
	if (pq->head == NULL)
	{
		pq->tail = NULL;
	}
}

//队列的大小
int QueueSize(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	int size = 0;
	while (NULL != cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

//队尾出数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	while (pq->head!=NULL)
	{
		QueueNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->tail = NULL;
}

test.c

c 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include"Queue.h"
void TestQueue1()
{
	Queue q;
	QueueInit(&q);

	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);

	QDataType ret = QueueFront(&q);
	printf("%d ", ret);

	QueuePop(&q);
	QueuePop(&q);
	QueuePop(&q);
	QueuePop(&q);
	QueuePop(&q);
}

void TestQueue2()
{
	Queue q;
	QueueInit(&q);

	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);

	int size = QueueSize(&q);
	printf("%d ", size);

	QDataType ret = QueueBack(&q);
	printf("%d ", ret);

	QueueDestroy(&q);
}
int main()
{
	//TestQueue1();
	TestQueue2();
	return 0;
}
相关推荐
2501_944521596 小时前
Ramda 跨端三方库仓颉适配全流程
开发语言·javascript·flutter·php
行思理6 小时前
go语言学习难度及适合学习的人群
开发语言·学习·golang
wangchunyu1148 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
程序 代码狂人9 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
meiying883810 小时前
快递单号批量查询Python脚本:从手动到自动化的完整实现
开发语言·python·自动化
@大迁世界11 小时前
告别 Go,拥抱 Rust
开发语言·golang·rust
SEO_juper11 小时前
Node.js 重定向链检测工具:301 跳了几跳、有没有成环,一次看清
xml·开发语言·python·node.js·json·bash
学逆向的11 小时前
C 指针常量与常量指针
c语言·开发语言·网络安全
Li_RuiQi11 小时前
DAgger方法演进_独立学习笔记
笔记·学习·计算机视觉
低调滴开发12 小时前
3 行代码写崩一个函数?切片才是 Rust 字符串处理的正确姿势
开发语言·后端·rust