剑指offer --- 用两个栈实现队列的先进先出特性

目录

前言

一、读懂题目

二、思路分析

三、代码呈现

总结


前言

        当我们需要实现队列的先进先出特性时,可以使用栈来模拟队列的行为。本文将介绍如何使用两个栈来实现队列,并给出具体的思路和代码实现。


一、读懂题目

题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。

        当我们需要利用栈来实现队列先进先出的特性时,考虑到单个栈对存入的一组数据push后再逐位pop取出,对应的序列和输入的顺序相反,那我们是否可以用两个栈,抽象类似于负负得正的手法,使得top位逐个取出得到的序列和插入时是相同的?

二、思路分析

首先基础类定义如下,我们只需要补充里面 appendTail 和 deleteHead 两函数的定义即可:

// 用两个栈实现队列
template<typename T>
class MyQueue
{
public:void appendTail(const T& n);T deleteHead();
private:stack<T> st1;stack<T> st2;};

为了便于表示和说明,设定一组数据 {a, b, c, d, e} 作为测试序列。

我们看到两个栈都可存储数据,那我们不妨先将数据全部存入st1中,那么根据栈先进后出的特性,两栈中数据的存储此时应该是这样的:

既然我们想到试试“负负得正”这种方法,那要是把st1中的元素再一 一取出放入st2中,是不是相当于把序列的顺序调转再调转,这样当我们不断取 st2.top() 时,最后按照取出的序列就是原来顺序的初始序列!

我们模拟一下这个过程:

1)对st1执行两次取首压入st2中:

2)将剩余元素从st1中全部压入st2中,此时st1为空:

 3)最后每当该模拟队列执行一次 front() 操作就返回 top2 所指向的值,每次 pop() 就从 st2 取出栈顶元素(前提是st1为空),直到 st2 栈为空:

那如果在执行 front() 过程中,夹杂着新的push()指令,应该将新元素放入st1还是st2呢?如果st1不为空,要先将st1全部按照上面的规则移入st2后再插入还是先放入st1栈顶,再统一移入st2呢?

首先我们明确每个元素都需要经历“负负得正”的过程,所以首先新加元素肯定要先经过st1才能进入st2中。那是否需要st1及时清空呢?

我们不妨以 {a, b, c, d}, {e} 模拟两批次对模拟队列执行push()指令:

假定我们在每批操作结束就将该批的元素依次弹出并置入st2中,那么当需要输入上面第二组(即{e})时,st1和st2的存储情况如下:

接下来该插入第二批数据了,但是实际功能调用过程中,不可能每次单批次插入操作之间是连续的,中间可能会有删除队列中已有元素的操作,所以我们不妨在两次 push() 指令中间加一句 pop_front() 指令,那么当接着执行一次pop_front() 指令时,对于st2而言应该将 a 元素剔除。同时我们注意到 a 元素正如我们所料位于st2的栈顶,当 st2.pop() 操作执行时,取出 a 元素,符合队列先进先出的特性。此时st1为空,st2顶部元素为b,如下图:

下一步我们进行第二批插入,将元素 e 执行 push() 操作时,显然 e 需要先进st1中,所以st1中此时不为空,元素e即为st1栈顶元素:

那么此时我们要不要把st1中的所有元素(这里仅有 e )顺手植入st2中呢?

1)如果移入st2中:

现在面临一个问题:如果我们一次性取出st2中的元素,亦或是仅取st2栈顶元素执行 st2.pop() 来获得理想的队列头,却发现得到的结果并不满足我们的设计需求,两轮输入的总序列为 {a, b, c, d, e} ,而取出序列为 {a, e, ... },显然不符合先进先出的原则。

那是否意味着第二批的输入元素应该先保存在st1中,并非单批输入结束后就将其接着压入st2中,那为什么首批输入的数据就可以直接经st1后全部置入st2呢?

难道因为开始时候 st2 为空吗?那如果后续其他批次插入时,也遵循 st2 为空后再将 st1 中所有元素置于 st2 中,会不会发生这样的冲突?

不妨我们按照这样的设计思路进行测试:

首先给出部分功能的此思路代码:

1)清空 st1 容器功能代码

template<typename T>
void MyQueue<T>::appendOver()
{if (st2.empty())    // 只有满足st2为空才清空st1{while (!st1.empty()){st2.push(st1.top());st1.pop();}}
}

 2)模拟 front() , push() 和 pop() 的函数功能

template<typename T>
T& MyQueue<T>::front()
{if (!st1.empty()){appendOver();}assert(!st2.empty());return st2.top();
}template<typename T>
void MyQueue<T>::appendTail(const T& n)
{st1.push(n);
}template<typename T>
T MyQueue<T>::deleteHead()
{assert(!empty());     // 不能同时为空if (st2.empty()){appendOver();}T tmp_poped = st2.top();st2.pop();return tmp_poped;
}

 3)实际测试代码

void test1()
{MyQueue<int> mq;// queue.push()模拟尾插for (int i = 10; i > 0; i--){mq.appendTail(i);}			// 1 2 3 4 5 6 7 8 9 10 右侧栈顶// queue.front()模拟取首元素// appendTail()--push()模拟入队列操作// deleteHead()--pop()模拟删除队列首元素mq.deleteHead();		// st1中:空	    st2中:1 2 3 4 5 6 7 8 9 右侧栈顶mq.appendTail(100);		// st1中:100	st2中:1 2 3 4 5 6 7 8 9 右侧栈顶// 由于st2不为空,st1中元素不发生迁移// 经过上面两步 st1中:100	st2中:1 2 3 4 5 6 7 8 9// 假设我们再进行一组类似操作mq.deleteHead();		// st1中:100		st2中:1 2 3 4 5 6 7 8 右侧栈顶mq.appendTail(55);		// st1中:55 100	    st2中:1 2 3 4 5 6 7 8 右侧栈顶int val = mq.deleteHead();	// st1中:55 100	st2中:1 2 3 4 5 6 7 右侧栈顶printf("val = %d\n", val);	// 8PopAndPrintMyQueue<int>(mq);
}

按理来说结果应该和各行代码后面的理想推测相同,我们看看最后两行执行结果:

可以看到和我们推测的结果完全一致,我们不仅在上面完成了一组插入删除后,额外执行了一组插入和删除操作,最后打印整个模拟队列中剩余元素时,呈现的结果和传入的顺序也相同,同样可以采用其他各种操作,统计每次取出的元素组成的序列是否满足先进先出的特性,结果终究是符合的。

三、代码呈现

下面直接给出代码:

// 用两个栈实现队列
template<typename T>
class MyQueue
{
public:void appendTail(const T& n);T deleteHead();T& front();bool empty();
private:void appendOver();     // 将st1中元素的全部移入st2中 --- 前提:st2不为空stack<T> st1;stack<T> st2;};template<typename T>
void MyQueue<T>::appendTail(const T& n)
{st1.push(n);
}template<typename T>
void MyQueue<T>::appendOver()
{if (st2.empty())    // 只有满足st2为空才清空st1{while (!st1.empty()){st2.push(st1.top());st1.pop();}}
}template<typename T>
bool MyQueue<T>::empty()
{if (st1.empty() && st2.empty()){return true;}return false;
}template<typename T>
T MyQueue<T>::deleteHead()
{assert(!empty());     // 不能同时为空if (st2.empty()){appendOver();}T tmp_poped = st2.top();st2.pop();return tmp_poped;
}template<typename T>
T& MyQueue<T>::front()
{if (st2.empty()){appendOver();}assert(!st2.empty());return st2.top();
}template<typename T>
void PopAndPrintMyQueue(MyQueue<T>& my_q)
{while (!(my_q.empty())){cout << my_q.front() << "\t";my_q.deleteHead();}cout << endl;
}void test1()
{MyQueue<int> mq;// queue.push()模拟尾插for (int i = 10; i > 0; i--){mq.appendTail(i);}			// 1 2 3 4 5 6 7 8 9 10 右侧栈顶// queue.front()模拟取首元素// appendTail()--push()模拟入队列操作// deleteHead()--pop()模拟删除队列首元素mq.deleteHead();		// st1中:空	st2中:1 2 3 4 5 6 7 8 9 右侧栈顶mq.appendTail(100);		// st1中:100	st2中:1 2 3 4 5 6 7 8 9 右侧栈顶// 由于st2不为空,st1中元素不发生迁移// 经过上面两步 st1中:100	st2中:1 2 3 4 5 6 7 8 9// 假设我们再进行一组类似操作mq.deleteHead();		// st1中:100		st2中:1 2 3 4 5 6 7 8 右侧栈顶mq.appendTail(55);		// st1中:55 100	st2中:1 2 3 4 5 6 7 8 右侧栈顶int val = mq.deleteHead();	// st1中:55 100	st2中:1 2 3 4 5 6 7 右侧栈顶printf("val = %d\n", val);	// 8PopAndPrintMyQueue<int>(mq);
}

值得注意的是,上面对重要功能的安全性检查中,下面两种写法其本质是相同的:

// 1.
if (st2.empty())
{appendOver();
}
assert(!st2.empty());// 2.
assert(!empty());     // 不能同时为空
if (st2.empty())
{appendOver();
}

总结

        本文详细介绍了如何利用栈来实现队列的先进先出特性。通过使用两个栈,我们可以将插入的顺序和取出的顺序保持一致。文章讨论了具体的实现思路,并通过代码实例进行了测试。通过测试结果,我们验证了模拟队列的各种操作都满足先进先出的特性。这种使用栈实现队列的方法在实际应用中具有重要的意义。

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

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

相关文章

[ 云计算 | AWS ] AI 编程助手新势力 Amazon CodeWhisperer:优势功能及实用技巧

文章目录 一、Amazon CodeWhisperer 简介1.1 CodeWhisperer 是什么1.2 Amazon CodeWhisperer 是如何工作的 二、Amazon CodeWhisperer 的优势和功能2.1 Amazon CodeWhisperer 的优势2.2 Amazon CodeWhisperer 的代码功能 三、Amazon CodeWhisperer 安装3.1 安装到 IntelliJ IDE…

计算数组中每个元素的立方根numpy.cbrt()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 计算数组中每个元素的立方根 numpy.cbrt() [太阳]选择题 请问以下代码中执行语句输出结果是&#xff1f; import numpy as np a np.array([1, 8, 27]) print("【显示】a ",a) pr…

synchronized jvm实现思考

底层实现时&#xff0c;为什么使用了cxq队列和entryList双向链表&#xff1f;这里为什么不跟AQS中使用一个队列就行了&#xff0c;加了一个entryList的目的是为了什么&#xff1f; 个人理解这里多一个entryList&#xff0c;可能是用于减少频繁的cas操作。假设存在很多锁竞争时&…

【Linux】gcc/g++ gdb 使用

目录 1&#xff0c;背景知识 2&#xff0c;gcc 如何完成 1&#xff0c;预处理(进行宏替换) 2&#xff0c;编译&#xff08;生成汇编&#xff09; 3&#xff0c;汇编&#xff08;生成机器可识别代码&#xff09; 4&#xff0c;连接&#xff08;生成可执行文件或库文件&…

rk3588 usb网络共享连接

出门在外总会遇到傻 X 地方 没有能连接公网的 网口给香橙派连网 而我的香橙派5plus 没有wifi模块。。。话不多说 在手机上看一眼手机的mac地址&#xff0c; 在rk3588 上执行以下命令&#xff1a; sudo ifconfig usb0 down sudo ifconfig usb0 hw ether 58:F2:FC:5D:D4:7A //该m…

浅谈基于云计算的环境智能监控系统

随着经济的飞速发展&#xff0c;环境污染也越来越严重&#xff0c;环境监控成为了政府与社会关注的焦点。本文提出了一种基于云计算的环境智能监控系统——EasyCVR&#xff0c;该系统综合应用了传感器、云计算、大数据、人工智能等技术&#xff0c;具有实时、准确、高效的监控能…

ChatGPT简介及基本概念

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列点击跳转>ChatGPT和AIGC &#x1f449;关于作者 专…

HarmonyOS开发:动态共享包的依赖问题

一、共享包的依赖方式 在需要依赖的模块包目录下oh-package.json5文件中添加依赖&#xff1a; "dependencies": {"ohos/srpaasUI": "file:../../srpaasUI","ohos/srbusiness": "file:../../feature/srbusiness"} 引入之后…

Java源码分析:Guava之不可变集合ImmutableMap的源码分析

原创/朱季谦 一、案例场景 遇到过这样的场景&#xff0c;在定义一个static修饰的Map时&#xff0c;使用了大量的put()方法赋值&#xff0c;就类似这样—— public static final Map<String,String> dayMap new HashMap<>(); static {dayMap.put("Monday&q…

音频类型转换工具-可执行文件exe/dmg制作

朋友车载音乐需要MP3格式&#xff0c;想要个批量转换工具 准备工作 brew install ffmpeg --HEAD或者官网下载安装ffmpeg并配置环境conda install ffmpeg 或者pip install ffmpeg-python 音频类型转换程序.py文件 exe文件在windows下打包&#xff0c;dmg在macos下打包&#…

2023_“数维杯”问题B:棉秸秆热解的催化反应-详细解析含代码

题目翻译&#xff1a; 随着全球对可再生能源需求的不断增加&#xff0c;生物质能作为一种成熟的可再生能源得到了广泛的关注。棉花秸秆作为一种农业废弃物&#xff0c;因其丰富的纤维素、木质素等生物质成分而被视为重要的生物质资源。虽然棉花秸秆的热解可以产生各种形式的可…

001 opencv addWeighted

目录 一、环境 二、addWeighted函数 三、代码演示 一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、addWeighted函数 OpenCV中的cv.addWeighted函数是一个用于图像叠加的函数&#xff0c;它可以将两个具有相同尺寸和类型的图像按…

基于JavaWeb+SSM+社区居家养老服务平台—颐养者端微信小程序系统的设计和实现

基于JavaWebSSM社区居家养老服务平台—颐养者端微信小程序系统的设计和实现 源码获取入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 前言 在复杂社会化网络中&#xff0c;灵活运用社会生活产生的大数据&am…

农户建档管理系统的设计与实现-计算机毕业设计源码20835

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设农户建档管理系统。 本…

python时间变化与字符串替换技术及读JSON文件等实践笔记

1. 需求描述 根据预测出结果发出指令的秒级时间&#xff0c;使用时间戳&#xff0c;也就是设定时间&#xff08;字符串&#xff09;转为数字时间戳。时间计算转换过程中&#xff0c;出现单个整数&#xff08;例如8点&#xff09;&#xff0c;按字符串格式补齐两位“08”。字符…

iOS项目集成RN(0)

iOS原有项目集成RN 环境安装RN环境搭建Node & Watchman 安装 创建新应用iOS项目集成RN如果没有iOS项目&#xff0c;新建一个 swift&#xff0c; storyboard项目&#xff0c; 名字&#xff1a;RNTest新建一个 RNDemo目录&#xff0c;一个iOS子目录&#xff0c; 把iOS相关的拷…

波卡三季度报告:已实现白皮书目标,异步支持与应用链技术推进

作者&#xff1a;Nicholas Garcia&#xff0c;Messari 研究分析师 编译&#xff1a;OneBlock 来源&#xff1a;https://messari.io/report/state-of-polkadot-q3-2023 知名分析平台 Messari 发布了 Polkadot 2023 年第三季度报告&#xff0c;分析了波卡的关键数据指标以及网…

挑战视觉边界,探索图形验证码背后的黑科技

在日常生活中&#xff0c;我们登录网站或者其他平台时&#xff0c;在填写完账号密码之后&#xff0c;还会让我们填写4或6位的数字或者英文字母等&#xff0c;填写正确才能请求登录。这个其实是防止某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试&#xff0c;如下…

Java虚拟机运行时数据区结构详解

Java虚拟机运行时数据区结构如图所示 程序计数器 程序计数器&#xff08;Program Counter Register&#xff09;是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的字节码的行号指示器。 多线程切换时&#xff0c;为了能恢复到正确的执行位置&#xff0c;每条线程…

基于DE10-Standard Cyclone V SoC FPGA学习---开发板简介

基于DE10-Standard Cyclone V SoC FPGA学习---开发板简介 简介产品规格基于 ARM 的 HPS配置与调试存储器件通讯连接头显示器音频视频输入模数转换器开关、按钮、指示器传感器电源 DE10-Standard 开发板系统框图Connect HTG 组件配置设计资源其他资源 简介 开发板资料 见 DE10-…