【数据结构】栈和队列(链表模拟队列)

server/2024/9/19 18:48:20/ 标签: 数据结构, 链表, 队列, 单链表

 


学习本章节必须具备 单链表的前置知识,

建议提前学习:点击链接学习:单链表各种功能函数 细节 详解

本章节是学习用 链表模拟队列

1. 单链表实现队列 思路如下

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先 进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一 端称为队头


1.1 使用 数组 还是 链表 模拟 队列 结构?

因为需要 模拟队列 先进先出的 特性:队头 只能出,队尾 只能进

若 使用 数组模拟,每次 pop 队头操作,就需要 全部元素向前面移动,时间复杂度为 O(n)

综上,因为需要 考虑位置变化,选择 链表 实现 队列 较优


1.2. 需要使用 什么类型的 链表模拟队列

单向

带头 / 不带头 都可以 :因为哨兵位主要用于 双向链表 找尾 ,为了方便删除,这里差别不大

不循环

我们下面实现的 是 单向不带头不循环链表

实际上,单向或双向,循环或不循环,带头或不带头 完全取决于 你自己要实现一个功能的需求,不是说 一定要固定使用 哪一个套 ,需要灵活选择使用


1.3. 单向链表 实现队列链表节点结构体创建:

typedef int QDataType;
typedef struct QueueNode
{QDataType value;            // 节点数据struct QueueNode* next;     // 指向下一个节点
}QNode;

1.4. 考虑效率,创建 头尾指针结构体

因为 队列需要:队头 push,队尾 pop

涉及到对 链表 的 尾部操作必须意识到:需要先进行 找尾操作,时间复杂度为 O(n)

方案:因为涉及头尾频繁操作:可以 直接 同时定义 头指针 phead 和 尾指针 ptail

技巧:同类型的变量可以封装成一个结构体

因为 phead 和 ptail 是可以不断变化的,每个相关函数都需要同时传递 phead 和 ptail 两个变量

则可以将多个同类型的变量 封装成 一个结构体,方便操作

这样,传递变量时 直接传递一个 结构体的指针就行了

typedef struct Queue
{QNode* phead;QNode* ptail;
}Queue;
// 区别:减少了传递变量的数量,利于协助开发
// void QueuePush(QNode* phead, QNode* ptail);
void QueuePush(Queue* pq);
// void QueuePop(QNode* phead, QNode* ptail);
void QueuePop(Queue* pq);


1.5. Push / Pop :入队 和 出队操作

Push 在队尾入队,Pop 在队头出队

void QueuePop(Queue* pq)
{assert(pq);// pop 的删除操作 需要分类讨论:链表节点个数为 0、为 1、为 两个以上// 为 0 :直接判空,退出操作:phead == ptail == NULLassert(pq->phead);    // 头节点为空 就一定代表为空了// 为 1:phead == ptail  但是 phead != NULL 的情况:即一定指向一个节点if (pq->phead == pq->ptail && pq->phead != NULL) {free(pq->phead);pq->phead = pq->ptail = NULL;}else // 为 两个以上:先记录第二个节点,free 掉头节点,更新头节点{QNode* tmp = pq->phead->next;free(pq->phead);pq->phead = tmp;}
}

为什么 ” 头节点为空 或 尾节点为空 就一定代表链表为空了 “?


1.6. 观察上面代码:有需要 判断链表节点数量的 需求,为了简化代码与优化过程,可以 直接定义一个 size ,放进结构体中,时刻记录 链表节点数量

// 结构体更改:
typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;// 加入 size 后 的 Push 和 Pop 函数
void QueuePop(Queue* pq)
{assert(pq);assert(pq->phead);if (pq->size == 1) {free(pq->phead);pq->phead = pq->ptail = NULL;}else if (pq->size >= 2){QNode* next = pq->phead->next;  // 保留下一个free(pq->phead);pq->phead = next;}pq->size--;    // 注意 pop 代表弹出一个节点,数量 - 1
}void QueuePush(Queue* pq, QDataType x)
{assert(pq);// push 前先创建一个新节点QNode* newNode = (QNode*)malloc(sizeof(QNode));if (newNode == NULL) {perror("malloc fail");return;}newNode->value = x;newNode->next = NULL;if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空{pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接pq->ptail = newNode; // 重新更新尾节点}else  // 若链表为空,则 phead 和 ptail 都要 处理{pq->phead = pq->ptail = newNode;}pq->size++;   // 数量++
}


2. 综上所述,最终代码:

Queue.c

#include"Queue.h"// Push 入队,Pop 出队
void QueuePop(Queue* pq)
{assert(pq);assert(pq->phead);if (pq->size == 1) {free(pq->phead);pq->phead = pq->ptail = NULL;}else if (pq->size >= 2){QNode* next = pq->phead->next;  // 保留下一个free(pq->phead);pq->phead = next;}pq->size--;    // 注意 pop 代表弹出一个节点,数量 - 1
}void QueuePush(Queue* pq, QDataType x)
{assert(pq);// push 前先创建一个新节点QNode* newNode = (QNode*)malloc(sizeof(QNode));if (newNode == NULL) {perror("malloc fail");return;}newNode->value = x;newNode->next = NULL;if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空{pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接pq->ptail = newNode; // 重新更新尾节点}else  // 若链表为空,则 phead 和 ptail 都要 处理{pq->phead = pq->ptail = newNode;}pq->size++;   // 数量++
}// 初始化
void  QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;
}// 销毁链表:就是 单链表的 销毁操作
void QueueDestory(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur) {QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;  // 最后别忘了头尾指针置为 NULLpq->size = 0;
}// Front 返回队头元素
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->phead); // 若链表为空 自然没有头节点;return pq->phead->value;
}// Back 返回队尾元素
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->ptail); // 若链表为空 自然没有尾节点;return pq->ptail->value;
}// Empty 判断是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}// Size 返回节点数量
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}

Queue.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int QDataType;
typedef struct QueueNode
{QDataType value;struct QueueNode* next;
}QNode;typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;// 初始化
void  QueueInit(Queue* pq);   // Push 入队,Pop 出队
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);// Front 队头元素,Back 队尾元素
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);// Empty 判断是否为空,Size 返回节点数量
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);// 销毁链表
void QueueDestory(Queue* pq);

Main.c

#include"Queue.h"int main()
{Queue q;   // 创建队列结构体QueueInit(&q); // 初始化:用于初始化链表的头尾节点:phead  /  ptailfor (int i = 1; i <= 5; ++i) {  // 入队列 几个元素: 1 2 3 4 5QueuePush(&q, i); }// 一个个读取队列元素while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));QueuePop(&q);}QueueDestory(&q);return 0;
}

3. LeetCode:225.队列实现栈

使用两个队列实现栈

核心思路:

保持一个队列存数据,一个队列为空
push 入数据,入到不为空的队列
pop 出数据,将 非空队列中 前 n-1 个数据 导入 空队列

代码实现

// 以下均是 链式队列的 相关函数,复制粘贴过来罢了
///
typedef int QDataType;
typedef struct QueueNode
{QDataType value;struct QueueNode* next;
}QNode;typedef struct Queue
{QNode* phead;QNode* ptail;int size;
}Queue;void QueuePop(Queue* pq)
{assert(pq);assert(pq->phead);if (pq->size == 1) {free(pq->phead);pq->phead = pq->ptail = NULL;}else if (pq->size >= 2){QNode* next = pq->phead->next;  // 保留下一个free(pq->phead);pq->phead = next;}pq->size--;    // 注意 pop 代表弹出一个节点,数量 - 1
}void QueuePush(Queue* pq, QDataType x)
{assert(pq);// push 前先创建一个新节点QNode* newNode = (QNode*)malloc(sizeof(QNode));if (newNode == NULL) {perror("malloc fail");return;}newNode->value = x;newNode->next = NULL;if (pq->ptail) // 若 ptail != NULL 说明此时链表不为空{pq->ptail->next = newNode; // 旧的尾节点和一个新的点 进行链接pq->ptail = newNode; // 重新更新尾节点}else  // 若链表为空,则 phead 和 ptail 都要 处理{pq->phead = pq->ptail = newNode;}pq->size++;   // 数量++
}// 初始化
void  QueueInit(Queue* pq)
{assert(pq);pq->phead = pq->ptail = NULL;pq->size = 0;
}// 销毁链表:就是 单链表的 销毁操作
void QueueDestory(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur) {QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;  // 最后别忘了头尾指针置为 NULLpq->size = 0;
}// Front 返回队头元素
QDataType QueueFront(Queue* pq)
{assert(pq);assert(pq->phead); // 若链表为空 自然没有头节点;return pq->phead->value;
}// Back 返回队尾元素
QDataType QueueBack(Queue* pq)
{assert(pq);assert(pq->ptail); // 若链表为空 自然没有尾节点;return pq->ptail->value;
}// Empty 判断是否为空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}// Size 返回节点数量
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
// 以上均是 链式队列的 相关函数,复制粘贴过来罢了
//////
// 下面是题目主体
typedef struct {Queue q1, q2; // 创建两个队列
} MyStack;MyStack* myStackCreate() {MyStack* pst = (MyStack*)malloc(sizeof(MyStack)); // 创建一个栈QueueInit(&(pst->q1));QueueInit(&(pst->q2));return pst;
}void myStackPush(MyStack* obj, int x) {// push 到 不为空的 队列if(QueueEmpty(&(obj->q1))) {QueuePush(&(obj->q2), x);}else {QueuePush(&(obj->q1), x);}
}int myStackPop(MyStack* obj) {// 找到非空的 队列,将 size-1 个元素放进 另一个空队列,同时最后一个元素pop掉// 有两种情况:q1 为空,q2 不为空, q2 为空,q1 不为空// 可以先假设,后调整// 先假设 队列1 为空,队列2 不为空,后面判断后调整Queue* pEmptyQ = &(obj->q1);Queue* pNonEmptyQ = &(obj->q2);if(!QueueEmpty(&(obj->q1))){pEmptyQ = &(obj->q2);pNonEmptyQ = &(obj->q1);}// 将不为空队列 的前 n-1 个元素放进 空队列中while(QueueSize(pNonEmptyQ) > 1) {int x = QueueFront(pNonEmptyQ);QueuePush(pEmptyQ, x);QueuePop(pNonEmptyQ);}int t = QueueFront(pNonEmptyQ);QueuePop(pNonEmptyQ);return t;
}int myStackTop(MyStack* obj) {if(QueueEmpty(&(obj->q1))) {return QueueBack(&(obj->q2));}else {return QueueBack(&(obj->q1));}
}bool myStackEmpty(MyStack* obj) {return QueueEmpty(&(obj->q1)) && QueueEmpty(&(obj->q2)); // 两个都为空才是栈空
}void myStackFree(MyStack* obj) {if(QueueEmpty(&(obj->q1))) {QueueDestory(&(obj->q2));}else if(QueueEmpty(&(obj->q2))) {QueueDestory(&(obj->q1));}
}


4. LeetCode:223.栈实现队列

使用 两个栈 模拟队列

思路

定义一个 pushStack :专门用来接收 push入队列的 数据
定义一个 popStack :专门用来 pop 队列数据
当 popStack 为空时,此时需要 pop 操作,则将 pushStack 的数据全部 放进 popStack ,补充数据(注意是全部);若popStack 不为空,则进行 pop 操作即可
当 需要 push 操作,直接往 pushStack 中放数据即可

演示: push 2 次,pop 1 次,push 3 次, pop 3 次

【若文章有什么错误,欢迎评论区讨论或私信指出】 


http://www.ppmy.cn/server/15136.html

相关文章

2024年地质测绘、遥感与地理信息技术国际学术会议(GSRSGIT2024)

2024年地质测绘、遥感与地理信息技术国际学术会议(GSRSGIT2024) 会议简介 2024年地质测绘、遥感与地理信息技术国际学术会议(GSRSGIT2024)将在北京隆重举行。本次大会将汇集国内外地质、测绘、遥感、地理信息技术等领域的专家学者&#xff0c;共同探讨行业前沿技术和发展趋势…

CNPM、NPM 和 Yarn:JavaScript 包管理器的比较

在现代Web开发中&#xff0c;包管理器是不可或缺的工具&#xff0c;它们帮助开发者管理项目中使用的各种第三方库。在JavaScript世界里&#xff0c;最常见的包管理器有 NPM、Yarn 和 CNPM。本文将详细介绍这三者的不同之处&#xff0c;并用简单的例子来帮助初学者理解每种工具的…

C++|运算符重载(3)|日期类的计算

前面介绍了运算符重载相关规则和方法&#xff0c;今天用运算重载函数实现对日期类的操作。 目录 前面准备 实现功能&#xff1a; -运算符 Date类和int 相减 Date类和Date类相减 运算符 &#xff0c;-运算符 ,!运算符 >,>运算符 <,<运算符 &#xff0c;-…

javaEE初阶——多线程(九)——JUC常见的类以及线程安全的集合类

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 小比特 大梦想 此篇文章与大家分享多线程专题的最后一篇文章:关于JUC常见的类以及线程安全的集合类 如果有不足的或者错误的请您指出! 目录 3.JUC(java.util.concurrent)常见的类3.1Callable接口3.2 RentrantLoc…

鸿蒙HarmonyOS应用 - ArkUI组件

ArkUI组件 基础组件 Image 声明Image组件并设置图片源 网络权限&#xff1a;ohos.permission.INTERNET Image(scr: string | PixelMap | Resource)// 1. string&#xff1a;用于加载网络图片&#xff0c;需要申请网络权限 Image("https://xxx.png")// 2. PixelMap…

vue3中的ref、isRef、shallowRef、triggerRef和customRef

1.ref 接受一个参数值并返回一个响应式且可改变的 ref 对象。 ref 对象拥有一个指向内部值的单一属性 .value property &#xff0c;指向内部值。 例&#xff1a;此时&#xff0c;页面上的 str1 也跟着变化 <template><div><button click"handleClick&quo…

【WebRTC】【Unity】局域网UDP通信为何不通

【背景】 还是在研究Unity中实现VR桌面&#xff0c;希望能够通过UDP广播先找到所有活跃的Client。但是发现UDP广播并未能够成功传递给同一局域网正在运行的客户端。 【分析】 UDP信息在局域网不通可能有如下几个原因&#xff1a; 未连在同一个网段防火墙问题是否存在其它网…

前端 -- Flex布局

Flex布局&#xff08;Flexible Box Layout&#xff09;是一种CSS布局方式&#xff0c;旨在提供一种更有效的方式来布局、对齐和分配容器内项目的空间&#xff0c;即使它们的大小未知或是动态变化的。Flex布局能够让容器的子元素能够灵活地增长和缩小以最佳地填充可用空间。 Fl…

详解 C++ 实现K-means算法

一、K-means算法概述 K-means算法是一种非常经典的聚类算法&#xff0c;其主要目的是将数据点划分为K个集群&#xff0c;以使得每个数据点与其所属集群的中心点&#xff08;质心&#xff09;的平方距离之和最小。这种算法在数据挖掘、图像处理、模式识别等领域有着广泛的应用。…

Chrome 侧边栏开发示例

前言 最近做项目&#xff0c;需要开发浏览器扩展&#xff0c;但是考虑页面布局兼容性问题&#xff0c;使用了Chrome114开始的侧边栏&#xff0c;浏览器自带的能力毕竟不会出现兼容性问题&#xff0c;不过Chrome123开始&#xff0c;侧边栏居然又可以选择固定右侧扩展栏了&#…

NAT的知识点和实现

1.NAT的作用&#xff1a; &#xff08;1&#xff09;、把内网私网IP转换公网IP&#xff1b; &#xff08;2&#xff09;、隐藏内网&#xff0c;起到保护内网作用&#xff1b; &#xff08;3&#xff09;、适当的缓解的IPv4地址空间枯竭&#xff1b; &#xff08;4&#xff…

Lagent AgentLego 智能体应用搭建——笔记

Lagent & AgentLego 智能体应用搭建——笔记 一、智能体简介1.1、为什么要有智能体1.1.1、幻觉问题1.1.2、时效性1.1.3、可靠性 1.2、智能体的含义1.3、智能体的组成1.3.1、大脑1.3.2、感知1.3.3、动作 1.4、智能体范式1.4.1、AutoGPT1.4.2、Rewoo1.4.3、ReAct 二、Lagent …

C语言工程调用C++库解决方案

本文为C语言工程调用C库的解决方案。 应用场景&#xff1a; 需要C程序编译成的库提供函数接口&#xff0c;来解决C语言工程的需求。 人的出场顺序真的很重要&#xff0c;很多人如果换一个时间认识&#xff0c;换一个时间共处&#xff0c;一切都将是不一样的场景&#xff0c;不…

Composer初次接触

php一直都是简单处理一下单片机的后台服务&#xff0c;没什么深入研究 今天安装一个 php composer.phar require qiniu/php-sdkComposer完全不懂&#xff0c;照着一试&#xff0c;就报错了 - topthink/think-installer v1.0.12 requires composer-plugin-api ^1.0 -> found…

python爬虫插件XPath的安装

概要 XPath Helper是一款专用于chrome内核浏览器的实用型爬虫网页解析工具。XPath可以轻松快捷地找到目标信息对应的Xpath节点&#xff0c;获取xpath规则&#xff0c;并提取目标信息&#xff0c;并进行校对测试&#xff1b;可对查询出的xpath进行编辑&#xff0c;正确编辑的结…

Web集群_02

Web集群_01 Keepalived 概述 Keepalived实现了高可用集群 Keepalived最初是为LVS设计 , 专门监控各种服务器节点的状态 Keepalived 后加入了 VRRP 功能 , 防止单点故障 VRRP ( 虚拟冗余路由协议 ) VRRP能在不改变网组的情况下 , 将多台路由器虚拟成一个虚拟路由器 , 通过配…

Qt——设置字体样式

在Qt中&#xff0c;你可以通过设置字体颜色来将文本显示为灰色。你可以使用QPalette对象为特定的控件或整个应用程序的控件设置颜色。以下是一个如何将按钮字体设置为灰色的示例&#xff1a; #include <QApplication> #include <QWidget> #include <QPushButto…

Android Binder——Java获取系统服务(十七)

对于获取服务应该比较熟悉了吧,前面的很多内容都有获取系统服务的调用,这一篇我们就来详细介绍一下获取服务的流程。 一、获取系统服务 1、Java调用 java 中获取系统服务经常通过 getSystemService() 方法,并传入对应的服务名实现。 // 获取电源相关的服务 PowerManager …

Docker向harbor上传大镜像的413报错

文章目录 一、背景二、问题三、处理1.调整harbor相关大小2.正向代理的nginx参数 一、背景 最近遇到了个需求&#xff0c;某厂商的系统模块以容器模式部署在我们的内网环境中&#xff0c;厂商为我们提供了一个公网仓库&#xff0c;需要我们自己下载相关镜像。因此&#xff0c;获…

基于Splinter演示如何使用Chrome WebDriver

关注开源优测不迷路 大数据测试过程、策略及挑战 测试框架原理&#xff0c;构建成功的基石 在自动化测试工作之前&#xff0c;你应该知道的10条建议 在自动化测试中&#xff0c;重要的不是工具 Chrome WebDriver由selenium提供的chrome浏览器驱动&#xff0c;在使用它前&#x…