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

 


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

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

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

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.mzph.cn/news/826913.shtml

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

相关文章

线程互斥及基于线程锁的抢票程序

我们实现一个简单的多线程抢票程序。 #include<iostream> #include<thread> #include<unistd.h> #include<functional> #include<vector> using namespace std; template<class T> using func_tfunction<void(T)>;//返回值为void,…

XUbuntu18.04 源码编译Qt4.5.3的过程

由于新公司很多旧的软件都是基于这个版本做的嵌入式开发。 所以想要自己搭一套基于Linux的非嵌入式开发环境&#xff0c;方便用来调试和编译代码。 这样就可以完成在linux下开发&#xff0c;然后直接嵌入式打包&#xff0c;涉及到界面的部分就不需要上机调试看问题了。 所以…

Axure引用ECharts图表 解决火狐浏览器出错

Axure原型添加Echarts图表&#xff0c;没耐心看文章的可以直接下载示例 Axure中使用ECharts图表示例 1. 打开Axure新建页面 2. 页面添加元件 元件类型随意&#xff0c;矩形、动态面板、热区、图片 甚至段落都可以3. 命名元件 随意命名&#xff0c;单个页面用到多个图表时名…

机器学习-11-基于多模态特征融合的图像文本检索

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中图像文本检索技术。此技术把自然语言处理和图像处理进行了融合。 参考 2024年&#xff08;第12届&#xff09;“泰迪杯”数据挖掘挑战赛 图像特征提取&#xff08;VGG和Resnet特征提取卷积过程详解&…

Facebook账号运营要用什么IP?

众所周知&#xff0c;Facebook封号大多数情况都是因为IP的原因。Facebook对于用户账号有严格的IP要求和限制&#xff0c;以维护平台的稳定性和安全性。在这种背景下&#xff0c;海外IP代理成为了一种有效的解决方案&#xff0c;帮助用户避免检测&#xff0c;更加快捷安全地进行…

学习笔记:Vue2中级篇

Vue2 学习笔记&#xff1a;Vue2基础篇_ljtxy.love的博客-CSDN博客学习笔记&#xff1a;Vue2中级篇_ljtxy.love的博客-CSDN博客学习笔记&#xff1a;Vue2高级篇_ljtxy.love的博客-CSDN博客 Vue3 学习笔记&#xff1a;Vue3_ljtxy.love的博客&#xff09;-CSDN博客 文章目录 5.…

SpringCloud系列(5)--SpringCloud微服务工程公共部分提取

前言&#xff1a;在上一章节中我们创建了两个个SpringCloud工程&#xff0c;但在两个工程中分别存在着一些重复的部分&#xff0c;例如重复的实体类&#xff08;如图所示&#xff09;&#xff0c;这样会造成系统的冗余&#xff0c;所以我们需要把公共的类提取到一个工程里&…

JavaScript变量及数据类型

目录 概述&#xff1a; 变量&#xff1a; 前言&#xff1a; 变量的命名&#xff1a; 定义变量&#xff1a; 为变量赋值&#xff1a; 变量提升&#xff1a; let和const关键字&#xff1a; JS数据类型&#xff1a; 前言&#xff1a; typeof操作符&#xff1a; JS基本…

RK3588 Android13 鼠标风格自定义动态切换

前言 电视产品,客户提供了三套鼠标图标过来,要求替换系统中原有丑陋风格且要支持动态切换, 并且在 TvSetting 中要有菜单,客户说啥就是啥呗,开整。 效果图 test framework 部分修改文件清单 png 为鼠标风格资源图片,这里就不提供了,可自由找一个替换一下就行 framew…

渐进时间复杂度O(n)

基本操作数 算法的运行速度受计算机性能的影响&#xff0c;所以通常考虑算法效率的不是算法运行的实际用时&#xff0c;而是算法运行所需要进行的基本操作的数量。 像加减乘除、访问变量、给变量赋值等都可以看作基本操作。对基本操作的计数或是估测可以作为评判算法用时的指标…

Python turtle海龟绘制美国队长盾牌

使用Python中的turtle模块绘制美队盾牌 具体思路如下&#xff1a; 导入海龟库第1个圆&#xff1a;半径 200&#xff0c;红色填充第2个圆&#xff1a;半径 150&#xff0c;白色填充第3个圆&#xff1a;半径 100&#xff0c;红色填充第4个圆&#xff1a;半径 50&#xff0c;蓝色…

CSS基础常用属性之字体属性(如果想知道CSS的字体属性知识点,那么只看这一篇就足够了!)

前言&#xff1a;在我们学习CSS的时候&#xff0c;主要学习选择器和常用的属性&#xff0c;而这篇文章讲解的就是最基础的属性之一——文字属性。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSDN博客 废话不多说&#xff0c;让我们直…

【C++】C++11 包装器

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 function包装器 fu…

RHCE:网络服务综合项目

基础配置&#xff1a; 1.配置主机名&#xff0c;静态IP地址 2.开启防火墙并配置 3.部分开启SElinux并配置 4.服务器之间使用同ntp.aliyun.com进行时间同步 5.服务器之间实现SSH免密登录 业务需求&#xff1a; 1.Server-NFS-DNS主机配置NFS服务器&#xff0c;将博客网…

【Lattice FPGA 开发】Modelsim与Diamond联合仿真

本文讲解Modelsim与Diamond进行联合仿真步骤&#xff0c;以及对遇到问题的解决与说明。 文章目录 软件版本0. Diamond设置文件为仿真文件特别注意 1. Diamond设置仿真软件为Modelsim2. Modelsim编译Lattice的库文件2.1 新建文件夹存放库文件2.2 Modelsim中建立新的仿真库2.2.1…

千锤百炼之算法Scanner和System.out引起超时解决办法

题外话 觉得这个内容还是很关键的,过来写一下吧 本次内容有点抽象大家试着听一下 正题 做过算法题的人都知道,无论是在力扣还是牛客或者别的网站刷题,很多情况下都会遇到输入输出的情况,当我们用Scanner和System.out.print()就有可能产生超时问题 如下图 接下来会有一段代…

远程计算机或设备将不接受连接_解决方法

重启了下电脑遇到了无法联网的问题&#xff0c;解决方法如下&#xff1a; 打开“控制面板”&#xff1b; 打开Internet选项&#xff1b; 点击“连接”&#xff1b; 点击“局域网设置”&#xff1b; 设置选项为下图&#xff1a; 连接成功了&#xff1a; 原因&#xff1a; 打…

数字化到底具有何种魔力!成为跟上时代的必经之路?

数字化确实具有深远的魔力和吸引力&#xff0c;成为现代企业在跟上时代步伐、实现持续发展和创新的重要驱动力。相较于传统信息化&#xff0c;数字化转型能够为企业带来更为显著和全面的降本增效效应。 首先&#xff0c;数字化转型通过深度融合信息技术和管理标准化&#xff0c…

GPU版本torch使用教程

GPU版本torch使用教程 一、下载配置CUDA和CUDNN &#xff08;1&#xff09;进入cmd使用nvidia-smi.exe查看自己电脑支持的最新CUDA版本&#xff08;可以下载地低版本&#xff09;&#xff0c;如图&#xff1a; 也可以通过NVIDIA控制面板&#xff08;NVIDIA Control Panel&am…

HTML重要标签梳理学习

1、HTML文件的框架 使用VS Code编码时&#xff0c;输入!选中第一个&#xff01;就可以快速生成一个HTML文件框架。 2、标签 <hr> <!--下划线--> <br> <!--换行--> <strong>加粗</strong> &…