剑指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);	// 8
	PopAndPrintMyQueue<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);	// 8
	PopAndPrintMyQueue<int>(mq);
}

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

// 1.
if (st2.empty())
{
	appendOver();
}
assert(!st2.empty());

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

总结

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

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

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

相关文章

centos7 killall命令安装、使用

安装 在线安装命 输入下面命令 yum install psmisc -y Psmisc软件包包含三个帮助管理/proc目录的程序。 安装下列程序: fuser, killall,pstree和pstree.x11(到pstree的链接) fuser #显示使用指定文件或者文件系统的进程的PID。 killall #杀死某个名字的进程&#xff0c;它…

Vue ElementUI操作 和 Axios使用

目录 一、ElementUI 1.简介 : 2.安装 : 3.配置 : 4.使用 : 二、Axios 1.简介 : 2.安装 : 3.实例 : 3.1 数据准备 3.2 应用实例 3.3 内容补充 一、ElementUI 1.简介 : ElementUI&#xff0c;是一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。El…

Linux设置禁止SSH空密码登录

为什么要禁止SSH空密码登陆&#xff1f; 禁止SSH空密码登录的原因是出于安全考虑。如果允许使用空密码进行SSH登录&#xff0c;那么任何人都可以通过尝试使用空密码来尝试登录到系统&#xff0c;从而获取系统的访问权限&#xff0c;这显然是非常不安全的。 此外&#xff0c;使…

将 ONLYOFFICE 文档编辑器与 Node.js 应用集成

我们来了解下&#xff0c;如何将 ONLYOFFICE 文档编辑器与您的 Web 应用集成。 许多 Web 应用都可以从文档编辑功能中获益。但是要从头开始创建这个功能&#xff0c;需要花费大量时间和精力。幸运的是&#xff0c;您可以使用 ONLYOFFICE——这是一款开源办公套件&#xff0c;可…

【完美世界】石昊身上宝术至尊骨、上苍之手和轮回宝术哪个最强

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 完美世界动画中&#xff0c;石昊通过举起天人族的镇教之宝飞仙石&#xff0c;终于补全了第一块至尊骨的天赋宝术-上苍之手。然而&#xff0c;这只是开始&#xff0c;上苍之手的终极奥义还需要他慢慢领悟。 在…

依赖注入方式

依赖注入方式 思考&#xff1a;向一个类中传递数据的方式有几种&#xff1f; 普通方法&#xff08;set方法&#xff09;构造方法 思考&#xff1a;依赖注入描述了在容器中建立bean与bean之间关系依赖的过程&#xff0c;如果bean运行需要的是数字或字符串呢&#xff1f; 引用类…

C/C++疫情集中隔离 2021年12月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C疫情集中隔离 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C疫情集中隔离 2021年12月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 A同学12月初从国外回来&#xff0c;按照防疫要…

uniapp-轮播图点击预览功能

实现效果 点击后打开预览图 实现代码 <swiper v-if"this.bannerList.length > 1" class"swiper" autoplay"true" duration"500" interval"2000" change"changeSwiper"><swiper-item class"swip…

Apache Airflow (九) :Airflow Operators及案例之BashOperator及调度Shell命令及脚本

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

248: vue+openlayers 以静态图片作为底图,并在上面绘制矢量多边形

第248个 点击查看专栏目录 本示例是演示如何在vue+openlayers项目中以静态图片作为底图,并在上面绘制矢量多边形。这里主要通过pixels的坐标作为投射,将静态图片作为底图,然后通过正常的方式在地图上显示多边形。注意的是左下角为[0,0]。 直接复制下面的 vue+openlayers源代…

【蓝桥杯省赛真题01】C++水下探测器 第十届蓝桥杯中小学生创意编程大赛C++编程比赛省赛真题解析

目录 C/C++水下探测器 一、题目要求 1、编程实现 2、输入输出 二、算法分析

The import xxx.xxx.xxxx is never used

CTRL SHIFT O 就完成了&#xff0c;懒人&#xff0c;代码没洁癖啊&#xff0c;几千上万的代码没用的。

再学动态规划

先用一张图来理一下动态规划大纲 参考&#xff1a;https://www.zhihu.com/question/291280715/answer/1007691283 动态规划五个步骤 参考&#xff1a;https://www.zhihu.com/question/25814123 ①判断题目能否用动规解法 ②确定状态 最后一步 子问题 ③转移方程 ④确定初始条…

SSL证书哪个品牌最好用?

现在市面上的SSL证书品牌有很多&#xff0c;选购SSL证书时有很多人并不是很清楚&#xff0c;因此有很多伙伴对于选择哪个SSL证书品牌而感到疑惑。今天JoySSL小编就专门介绍下哪些比较好用的SSL证书品牌。 SSL证书兼容性主要包含操作系统、浏览器、服务器三个方面&#xff0c;好…

Python----图像的手绘效果

图像的数组表示 图像是有规则的二维数据&#xff0c;可以用numpy 库将图像转换成数组对象 : from PIL import Image import numpy as np imnp.array(Image.open("D://np.jpg")) print(im.shape,im.dtype)结果&#xff1a; 图像转换对应的ndarray 类型是3 维数据&am…

OpenAI的Whisper蒸馏:速度提升6倍的Distil-Whisper

1 Distil-Whisper诞生 Whisper 是 OpenAI 研发并开源的一个自动语音识别&#xff08;ASR&#xff0c;Automatic Speech Recognition&#xff09;模型&#xff0c;他们通过从网络上收集了 68 万小时的多语言&#xff08;98 种语言&#xff09;和多任务&#xff08;multitask&am…

基于STM32的蓝牙低功耗(BLE)通信方案设计与实现

蓝牙低功耗&#xff08;Bluetooth Low Energy&#xff0c;简称BLE&#xff09;是一种能够在低功耗环境下实现无线通信的技术。在物联网应用中&#xff0c;BLE被广泛应用于传感器数据采集、健康监测设备、智能家居等领域。本文将基于STM32微控制器&#xff0c;设计并实现一个简单…

【FPGA】Verilog:升降计数器 | 波纹计数器 | 约翰逊计数器 | 实现 4-bit 升降计数器的 UP/DOWN

目录 Ⅰ. 理论部分 0x00 升降计数器&#xff08;UP DOWN Counter&#xff09; 0x01 波纹计数器&#xff08;Ripple Counter&#xff09; 0x02 约翰逊计数器&#xff08;Johnson Counter&#xff09; Ⅱ. 实践部分 0x00 实现&#xff1a;升降计数器&#xff08;4-bit&…

cvf_使用lora方法增强能力

cvf_使用lora方法增强能力 实验对比图最终代码简介详细解析实验对比图 最终代码 import paddle import numpy as np import pandas as pd from tqdm import tqdmclass FeedFroward(paddle.nn.Layer)

基于SDN技术构建多平面业务承载网络

随着企业数字化的浪潮席卷各个行业&#xff0c;传统网络架构面临着更为复杂和多样化的挑战。企业正在寻找一种全面适应数字化需求的网络解决方案。随着软件定义网络&#xff08;SDN&#xff09;的发展&#xff0c;“多业务SDN一张网”解决方案为企业提供了一种全新的网络架构&a…