【数据结构】链队列的C语言实现

news/2024/5/20 23:53:48/文章来源:https://blog.csdn.net/m0_73258399/article/details/129285591

队列

1.队列的概念

队列 和栈一样,是一个 特殊的线性表

队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。进行 插入操作 的一端称为 队尾,进行 删除操作 的一端称为队头

队列中的元素遵守 先进先出(First In First Out) 的原则。就和排队一样,队列是绝对公平的,先来的先到队头,不存在插队行为,只能后面排队,前面离开。
在这里插入图片描述

2.队列的结构

队列的结构可以用 数组链表 来实现。哪个结构更好?

数组
数组左边为队头右边为队尾:队尾(右)插入数据顺序表尾插 很方便,但是 队头(左)删除数据 需要挪动数据,很麻烦。
数组左边为队尾右边为队头:队头(右)删除数据 为尾删,队尾(左)插入数据 需要挪动数据,也很麻烦。
所以 数组结构 并不适合队列。

链表
结构选择:单/双 循环/非循环 带头/不带头
带不带头?可要可不要,带头就是方便尾插,少一层判断,没什么影响。
双向吗?没必要找前一个元素,队列只需要队头队尾出入数据。
循环吗?价值不大。双向循环可以找尾,但是没有必要。

双向链表
可以使用双向链表,但是没必要,小题大做了,使用单链表就可以。

3.队列的实现

3.1结构设计

上面确定了用 单链表实现,所以就一定要有结构体表示 节点

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

由于链表的尾插比较麻烦,而队列的入数据为尾插。所以定义队列的结构体时,可以定义两个指针 headtail 分别对应 队头队尾 ,tail 的引入就是方便尾插,再在给定一个 sz 实时记录队列的 大小

typedef struct Queue
{QNode* head;QNode* tail;int sz;
}Queue;

看到这样的结构可能会有疑问:
1.为什么会有两个结构体?struct QueueNode是整体,是存数据和下一个节点链接的;struct Queue是局部,是头指针和存size的。
2.为什么不直接合并两个结构体呢?struct QueueNode是整体,struct Queue是局部,不能和在一起,同时也体现了使用数据结构的灵活性

3.2接口总览

void QueueInit(Queue* pq); // 初始化
void QueueDestroy(Queue* pq); // 销毁
void QueuePush(Queue* pq, QDataType x); // 入队列
void QueuePop(Queue* pq); // 出队列
QDataType QueueFront(Queue* pq); // 取队头元素
QDataType QueueBack(Queue* pq); // 取队尾元素
bool QueueEmpty(Queue* pq); // 判空
int QueueSize(Queue* pq); // 计算队列大小

3.3初始化

队列初始化,就只需要结构中指针初始化为 NULL,并将 sz 初始化为0。

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

这里是通过结构体的地址来找到结构体中的两个指针,通过结构体来改变指针的。

3.4销毁

我们实现的队列结构为 链式 的,所以本质为一条 单链表
那么销毁时,就需要迭代销毁链表的节点。

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->sz = 0;
}

3.5入队列

对于单链表的尾插,需要创建节点,找尾,然后链接。

但是我们设计队列结构时,给定了一个 tail 作为队尾,这时插入就比较方便了。但是需要注意一下 第一次尾插 的情况。

在 入队列 之后,记得调整 sz

而且队列只会从队尾入数据,所以创建节点的一步,并没有必要封装一个接口专门调用,直接在函数中创建即可。

void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");exit(-1);}else{newnode->data = x;newnode->next = NULL;//不置空newnode->next是随机值,会出问题}// 尾插if (pq->head == NULL){assert(pq->tail == NULL);//头为空,尾却没为空,警告pq->head = pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = pq->tail->next;}pq->sz++; 
}

3.6 出队列

首先明确,队列为空不能出队列,出队列是从 队头 出数据。
其次,需要考虑删除时会不会有什么特殊情况。
一般删除时,可以记录 head 的下一个节点,然后释放 head ,再重新为 head 赋值。

但是,当 只有一个节点 呢?此刻 head == tail,它们的地址相同,如果此时仅仅释放了 head,最后head走到 NULL,但是tail 此刻指向被释放的节点,且没有置空,此刻风险就产生了,tail就变成野指针了。
在这里插入图片描述

之后一旦我 入队列 时,tail != NULL,那么必定就会走到 else 部分,对 tail 进行访问,此刻程序就会奔溃,所以需要处理一下,将 tail 也置空

同样的,出队列 成功后 sz 需要发生调整。

void QueuePop(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));// 一个节点时删除的特殊情况// 需要将头尾都变为空if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QNode* newhead = pq->head->next;free(pq->head);pq->head = newhead;}pq->sz--;
}

3.7取对头数据

队列非空,取 head 出数据

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

3.8 取队尾数据

队列非空,取 tail 处数据。

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

3.9 判空

当 head 和 tail 都为空指针时,说明队列中无元素。

bool QueueEmpty(Queue* pq)
{assert(pq);return pq->head == NULL && pq->tail == NULL;
}

3.10 计算队列大小

从这个接口处,就可以看出设计结构时,设计的还是很精妙的,因为有 sz 直接返回即可。

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

试想一下,如果没有这个 sz,我们如何计算队列大小?是不是又得遍历链表了?这样接口的时间复杂度就为O(N),而其他接口几乎都是O(1)的复杂度,是不是不太理想?所以结构设计时加上一个 sz 的效果是极好的!

下面贴上没有 sz 时的代码:

int QueueSize(Queue* pq)
{assert(pq);QNode* cur = pq->head;int size = 0;while (cur){cur = cur->next;++size;}return size;
}

4. 完整代码

Queue.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int QDataType;typedef struct QueueNode
{QDataType data;struct QueueNode* next;
}QNode;typedef struct Queue
{QNode* head;QNode* tail;int sz;
}Queue;void QueueInit(Queue* pq); // 初始化
void QueueDestroy(Queue* pq); // 销毁
void QueuePush(Queue* pq, QDataType x); // 入队列
void QueuePop(Queue* pq); // 出队列
QDataType QueueFront(Queue* pq); // 取队头元素
QDataType QueueBack(Queue* pq); // 取队尾元素
bool QueueEmpty(Queue* pq); // 判空
int QueueSize(Queue* pq); // 计算队列大小

Queue.c

#include "Queue.h"void QueueInit(Queue* pq)
{assert(pq);pq->head = pq->tail = NULL;pq->sz = 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->sz = 0;
}void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");exit(-1);}else{newnode->data = x;newnode->next = NULL;//不置空newnode->next是随机值,会出问题}// 尾插if (pq->head == NULL){assert(pq->tail == NULL);//头为空,尾却没为空,警告pq->head = pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = pq->tail->next;}pq->sz++;
}void QueuePop(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));// 一个节点时删除的特殊情况// 需要将头尾都变为空if (pq->head->next == NULL){free(pq->head);pq->head = pq->tail = NULL;}else{QNode* newhead = pq->head->next;free(pq->head);pq->head = newhead;}pq->sz--;
}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;
}bool QueueEmpty(Queue* pq)
{assert(pq);//return pq->size==0;return pq->head == NULL && pq->tail == NULL;
}int QueueSize(Queue* pq)
{assert(pq);return pq->sz;
}

test.c

#include "Queue.h"int main()
{Queue q;QueueInit(&q);QueuePush(&q,1);QueuePush(&q,2);QueuePush(&q,3);QueuePush(&q,4);while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));QueuePop(&q);}printf("\n");QueueDestroy(&q);system("pause");return 0;
}

7.总结:

今天我们认识并学习了队列的相关概念、结构与接口实现,并且针对每个常用的功能接口进行了实现。总体来说,链队列的结构相比于之前的数据结构是比较简单的,之后将介绍和讲解栈与队列的相关OJ题。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_274434.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【类的继承与派生的知识点】

文章目录类的继承与派生的知识点类的继承与派生&#xff1a;类成员的访问&#xff1a;类型兼容规则&#xff1a;一个公有派生类的对象在使用上可以被当成基类的对象&#xff0c;反之不行单继承与多继承派生类的构造与析构类成员的标识与访问类的继承与派生的实验结果类型兼容规…

Baumer工业相机堡盟相机如何使用Sharpening图像锐化功能( Sharpening图像锐化功能的优点和行业应用)(C++)

项目场景 Baumer工业相机堡盟相机是一种高性能、高质量的工业相机&#xff0c;可用于各种应用场景&#xff0c;如物体检测、计数和识别、运动分析和图像处理。 Baumer的万兆网相机拥有出色的图像处理性能&#xff0c;可以实时传输高分辨率图像。此外&#xff0c;该相机还具…

【计算机网络】如何解决TCP粘包问题?

【计算机网络】如何解决TCP粘包问题&#xff1f; 文章目录【计算机网络】如何解决TCP粘包问题&#xff1f;如何理解字节流&#xff1f;如何解决粘包&#xff1f;固定长度的消息特殊字符作为边界自定义消息结构如何理解字节流&#xff1f; 之所以会说 TCP 是面向字节流的协议&a…

RK3588编译环境Ubuntu20.04编译配置-增加交换内存

迅为提供的编译环境 Ubuntu20.04 默认配置了交换内存是 9G&#xff0c;如果在编译过程中&#xff0c;因内 存不够而编译报错&#xff0c;可以参考本小节进行设置。 这里举例分配 5G 交换内存。 在开始之前&#xff0c;使用命令检查一下您的 ubuntu 的 swap 分区。 sudo swa…

Android进阶面经,面试10余家经验分享,拿到offer真不难~

前言 我们都知道面试大厂主要就是考察程序员技术方向的专业技能&#xff0c;Java开发主要考察的就是Java方面的专业技能&#xff0c;而Android岗位的 专业技能 就是Android程序员面试的重要考察方向。 大厂的招聘条件是明牌的&#xff0c;但技术这一块却难倒了大部分的人。 面…

蓝桥杯刷题冲刺 | 倒计时18天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;马上就要蓝桥杯了&#xff0c;最后的这几天尤为重要&#xff0c;不可懈怠哦&#x1f43e; 文章目录0.知识点1.乳草的入侵今天写 搜索题 0.知识点 DFS 设计步骤 确定该题目的状态&#xff08;包括边…

服务器boa移植

服务器boa移植 文章目录服务器boa移植1.下载boa2.解压3.安装词法解析器4.修改源码5. 编译、创建配置文件6.修改配置文件boa.conf7.运行测试1.下载boa Boa Webserver http://www.boa.org/ 2.解压 tar -xf boa-0.94.13.tar.gz3.安装词法解析器 sudo apt-get install bisonsud…

我们为什么不能忽视业务只讲数据治理?_光点科技

数据治理是一项重要的业务实践&#xff0c;可以帮助组织更好地管理和利用数据。然而&#xff0c;一些企业错误地将数据治理视为一项独立的技术实践&#xff0c;而忽略了业务需求。那么&#xff0c;为什么不能忽视业务&#xff0c;只讲数据治理呢&#xff1f;首先&#xff0c;数…

网络基础知识和常用命令

IP、子网掩码、网关、DNS、端口号网络的基本概念客户端:应用 C/S&#xff08;客户端/服务器&#xff09; B/S&#xff08;浏览器/服务器&#xff09;服务器&#xff1a;为客户端提供服务、数据、资源的机器请求&#xff1a;客户端向服务器索取数据响应&#xff1a;服务器对客户…

H2数据库

H2是一个用Java开发的嵌入式数据库&#xff0c;它本身只是一个类库&#xff0c;可以直接嵌入到应用项目中。 H2简介 H2是一个Java编写的关系型数据库&#xff0c;它可以被嵌入Java应用程序中使用&#xff0c;或者作为一个单独的数据库服务器运行。 H2数据库的前身是 Hypersoni…

线段树SegmentTree

&#x1f34f;&#x1f350;&#x1f34a;&#x1f351;&#x1f352;&#x1f353;&#x1fad0;&#x1f951;&#x1f34b;&#x1f349;&#x1f95d; 什么是线段树&#xff0c;它能解决什么样的问题&#xff1f; 文章目录&#x1f36d;问题引入&#x1f95d;线段…

代码随想录|day21|二叉树part07 ● 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ● 236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差 链接&#xff1a;代码随想录 需要领悟一下二叉树遍历上双指针操作&#xff0c;优先掌握递归 第一次做&#xff0c;理解错误&#xff0c;认为只需要以节点为单位&#xff0c;认为由于是二叉搜索树&#xff0c;所以中序遍历一定是一个连续的有序序列…

vue3+vite+ts 搭建脚手架01创建vite项目并且在项目中初次使用router

vue3vite 搭建脚手架01创建vite项目并且在项目中使用router 1.使用yarn安装vite项目 yarn create vite 搭建vite项目 在开发语言中选择vuets2.安装现在最新的 vue-router4 yarn add vue-router4 在packger中检查是否成功安装3.简单配置router文件 在项目中新建views和…

(19)C#传智:CSS,选择器,样式(第19天)

vs2022保存html项目时&#xff0c;偶尔会有死机&#xff0c;只得强行关闭重新打开。 一、CSS简介 CSS(Cascading Style Sheet)层叠样式表。能让网页制作者有效的定制&#xff0c;改善网页的效果。 CSS是对Html的补充&#xff0c;它很好地控制了网页的显示效果。并实现网页…

A.[OCR]基于PaddleOCR的多视角集装箱箱号检测识别,实现检测识别模型串联推理。

基于PaddleOCR的多视角集装箱箱号检测识别 一、项目介绍 集装箱号是指装运出口货物集装箱的箱号&#xff0c;填写托运单时必填此项。标准箱号构成基本概念&#xff1a;采用ISO6346&#xff08;1995&#xff09;标准 标准集装箱箱号由11位编码组成&#xff0c;如&#xff1a;…

UniApp + SpringBoot 实现接入支付宝支付功能和退款功能

一、支付宝开放平台设置 注册支付宝支付功能需要个体工商户或企业才可以&#xff01;需要有营业执照才能去申请哦&#xff01; 1、登录到控制台 进入支付宝开放平台 控制台 2、开发设置 3、产品绑定APP支付 如果没有绑定APP支付就会报商家订单参数异常&#xff0c;请重新发起…

Kubernetes学习(八)Helm应用包管理器

为什么需要Helm K8S上的应用对象&#xff0c;都是由特定的资源描述组成&#xff0c;包括deployment、service等。都保存各自文件中或者集中写到一个配置文件&#xff0c;然后kubectl apply –f 部署。 如果应用只由一个或几个这样的服务组成&#xff0c;上面部署方式足够了。…

华为OD机试题,用 Java 解【合并数组】问题 | 含解题说明

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典本篇题目:合并数组 题目 现在有多组整数…

Ubuntu+Nvidia驱动+cuda+cudnn环境配置

目录 Ubuntu系统安装 rtw89版本是需要一定高的内核版本的 Ubuntu Nvidia驱动安装 在附加驱动中安装完nvidia专用驱动&#xff0c;nvidia-smi没有显示驱动信息 安装cuda 安装cudnn Ubuntu系统安装 首先便是双系统的安装&#xff0c;我本身电脑是Windows&#xff0c;装Ubu…

JS: mac台式电脑使用汇总

双指鼠标左滑–回到桌面optaion键 相当于辅助键 optaiona复制 optionx剪切 …操作文件&#xff1a; 苹果文件夹是沙箱模式&#xff0c;要从一个文件夹拖动到另一个文件夹 —调试 iphone连接mac电脑(苹果safari浏览器-开发–xxx的电脑-hap)即可真机调试 –环境 MAC安装node环境:…