数据结构 —— 队列

本文主要讨论使用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); // 销毁队列
}

运行结果如下:

相关推荐
一个爱编程的人6 小时前
一个数是不是素数
数据结构·算法
忡黑梨7 小时前
eNSP_从直连到BGP全网互通
c语言·网络·数据结构·python·算法·网络安全
地球资源数据云7 小时前
1900-2023年中国物种分布点位矢量数据集
大数据·数据结构·数据库·数据仓库·人工智能
AI人工智能+电脑小能手8 小时前
【大白话说Java面试题】【Java基础篇】第20题:HashMap在计算index的时候,为什么要对数组长度做减1操作
java·开发语言·数据结构·后端·面试·哈希算法·hash-index
牢姐与蒯8 小时前
cpp数据结构之map
数据结构
故事和你918 小时前
洛谷-算法2-3-分治与倍增5
开发语言·数据结构·c++·算法·动态规划·图论
北顾笙9808 小时前
day37-数据结构力扣
数据结构·算法·leetcode
liuyao_xianhui10 小时前
进程概念与进程状态_Linux
linux·运维·服务器·数据结构·c++·哈希算法·宽度优先
如君愿10 小时前
考研复习 Day 26 | 习题--计算机网络第三章(数据链路层 下)、数据结构 多维数组与广义表
数据结构·计算机网络·考研·记录考研
bqq1986102610 小时前
MySQL分库分表
数据结构·mysql