数据结构 —— 队列

本文主要讨论使用c语言实现队列。

什么是队列?

上篇文章我们学习了栈以及实现;我们知道栈的元素进出规则是先进后出,而队列与栈正好相反,队列的规则是先进先出

就比如现在有一个排队的队伍,从末尾进去的元素会逐渐向另一端靠近,当达到头部的时候离开队伍。这就是先进先出。

所以,队列是一种先进先出的数据结构:

  • 入队:在队尾插入元素
  • 出队:在队头删除元素

实现

头文件

同样先来看头文件。我们使用链表来实现队列。如果是使用数组,则不方便实现先进先出的功能,需要使用循环数组来完成。我们先来看代码:

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int QType;

typedef struct Node
{
	QType data;
	struct Node* next;
}Node;

typedef struct Queue
{
	Node* front;
	Node* rear;
}Queue;

void QueueInit(Queue* q);
void QueueDestory(Queue* q);

void QueuePush(Queue* q, QType x);
void QueuePop(Queue* q);
int QueueSize(Queue* q);
int QueueEmpty(Queue* q);
QType QueueFront(Queue* q);
QType QueueBack(Queue* q);

这里我们创建了front和rear两个指针。链式队列必须同时维护 frontrear,否则就无法在 O(1) 时间内完成入队和出队。front表示头指针,rear表示尾指针。如果没有尾指针,那么插入操作每次都需要遍历链表找到尾部,这样的效率并不高。所以我们使用头尾指针来实现效率更高。

源文件

1、初始化

初始化很简单,只需要将头尾指针置空即可。

cpp 复制代码
void QueueInit(Queue* q)
{
	assert(q);
	q->front = q->rear = NULL;
}

2、销毁

销毁时需要将每个节点都销毁,因此这时必须进行遍历,并且,最后不要忘记将头尾指针置空防止造成野指针。

cpp 复制代码
void QueueDestroy(Queue* q)
{
	assert(q);

	Node* cur = q->front;
	while (cur)
	{
		Node* next = cur->next;
		free(cur);
		cur = next;
	}
	//释放完后front和rear仍然指向原来的旧值,会造成野指针
	//所以需要置空
	q->front = q->rear = NULL;
}

3、创建新节点

该部分我们已经写过很多遍了。这里就不再说明,直接上代码:

cpp 复制代码
Node* BuyNewNode(QType x)
{
	Node* newnode = (Node*)malloc(sizeof(Node));
	if (newnode == NULL)
	{
		perror("malloc failed!");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

4、插入新元素

队列中的元素总是从尾部插入。要注意的是,如果是空队列,还需要对front进行处理。代码如下:

cpp 复制代码
void QueuePush(Queue* q, QType x)
{
	assert(q);
	Node* newnode = BuyNewNode(x);
	if (newnode == NULL)
	{
		printf("创建节点失败!\n");
		return;
	}
	//如果是空队列
	if (q->rear == NULL)
	{
		q->front = q->rear = newnode;
		return;
	}
	q->rear->next = newnode;
	q->rear = newnode;
}

5、删除元素

删除元素时除了考虑空队列外海需要考虑当队列只有一个元素时,不能只处理front,rear也要一并处理。代码如下:

cpp 复制代码
void QueuePop(Queue* q)
{
	assert(q);
	if (q->front == NULL)
	{
		printf("队列为空!\n");
		return;
	}
	if (q->front == q->rear)
	{
		free(q->front);
		q->front = q->rear = NULL;
		return;
	}
	Node* next = q->front->next;
	free(q->front);
	q->front = next;
}

6、计算队列元素个数

如果我们想要得知队列的元素个数,在当前的情况下就需要遍历一遍队列来让标记递增。实现代码如下:

cpp 复制代码
int QueueSize(Queue* q)
{
	assert(q);
	if (q->front == NULL)
	{
		//空队列元素为0
		return 0;
	}
	Node* cur = q->front;
	int tmp = 0;
	while (cur)
	{
		tmp++;
		cur = cur->next;
	}
	return tmp;
}

不过,如果我们的队列对元素个数的获取使用十分频繁的话,为了保证O(1),我们直接在结构的时候多创建一个size变量用于记录即可:

cpp 复制代码
typedef struct Node
{
	QType data;
	struct Node* next;
    int size;//记录队列元素个数
}Node;

7、返回队首/队尾元素

返回元素十分简单,注意判空就行。直接上代码:

cpp 复制代码
QType QueueFront(Queue* q)
{
	assert(q);
	assert(q->front);
	return q->front->data;
}

QType QueueRear(Queue* q)
{
	assert(q);
	assert(q->rear);
	return q->rear->data;
}

8、判空函数

我们从上面的功能中发现需要判断队列为空的部分有很多,因此我们也可以单独写一个判空的函数,调用时使用assert断言即可判断。代码如下:

cpp 复制代码
int QueueEmpty(Queue* q)
{
	assert(q);

	return q->front == NULL;
}

测试

最后我们对代码进行测试。测试函数如下:

cpp 复制代码
void Test()
{
    Queue q;
    QueueInit(&q); // 初始化队列

    printf("队列是否为空?%s\n", QueueEmpty(&q) ? "是" : "否");

    // 入队
    printf("入队元素:1 2 3 4 5\n");
    for (int i = 1; i <= 5; i++)
    {
        QueuePush(&q, i);
        printf("队头: %d, 队尾: %d, 队列大小: %d\n",
            QueueFront(&q), QueueRear(&q), QueueSize(&q));
    }

    printf("\n出队\n");
    while (!QueueEmpty(&q))
    {
        printf("出队元素: %d\n", QueueFront(&q));
        QueuePop(&q);
        if (!QueueEmpty(&q))
            printf("队头: %d, 队尾: %d, 队列大小: %d\n",
                QueueFront(&q), QueueRear(&q), QueueSize(&q));
        else
            printf("队列为空\n");
    }

    // 再次判空
    printf("\n出队完成后,队列是否为空?%s\n", QueueEmpty(&q) ? "是" : "否");

    QueueDestroy(&q); // 销毁队列
}

运行结果如下:

相关推荐
驭渊的小故事1 小时前
简单模板笔记
数据结构·笔记·算法
VT.馒头1 小时前
【力扣】2727. 判断对象是否为空
javascript·数据结构·算法·leetcode·职场和发展
历程里程碑3 小时前
Linux 库
java·linux·运维·服务器·数据结构·c++·算法
Sheep Shaun3 小时前
如何让一个进程诞生、工作、终止并等待回收?——探索Linux进程控制与Shell的诞生
linux·服务器·数据结构·c++·算法·shell·进程控制
Pluchon3 小时前
硅基计划4.0 简单模拟实现AVL树&红黑树
java·数据结构·算法
小龙报3 小时前
【51单片机】从 0 到 1 玩转 51 蜂鸣器:分清有源无源,轻松驱动它奏响新年旋律
c语言·数据结构·c++·stm32·单片机·嵌入式硬件·51单片机
dllxhcjla3 小时前
数据结构和算法
数据结构
历程里程碑4 小时前
普通数组----轮转数组
java·数据结构·c++·算法·spring·leetcode·eclipse
sin_hielo4 小时前
leetcode 1653
数据结构·算法·leetcode
李日灐4 小时前
C++进阶必备:红黑树从 0 到 1: 手撕底层,带你搞懂平衡二叉树的平衡逻辑与黑高检验
开发语言·数据结构·c++·后端·面试·红黑树·自平衡二叉搜索树