【经典数据结构OJ讲解】你知道如何用两个队列实现一个栈,如何用两个栈实现一个队列吗?

news2025/7/23 6:08:54

目录

0.前言

1.回顾什么是队列和栈

2.如何用两个队列实现一个栈

2.1思路讲解

2.2按照思路实现仿生栈的各接口

2.2.1栈的初始化

2.2.2栈的销毁

2.2.3栈的插入

2.2.4栈的删除

2.2.5 栈的栈顶数据

2.2.6 判断当前栈是否为空

3.如何用两个栈实现一个队列

3.1 思路分析

3.2代码实现

3.2.1 封装该队列

3.2.2队列的初始化

3.2.3队列的销毁

3.2.4队列的插入

3.2.5队列的队头

3.2.6队列的删除

3.2.7队列的判空


0.前言

4栈和队列OJ题集合 · onlookerzy123456qwq/data_structure_practice_primer - 码云 - 开源中国 (gitee.com)https://gitee.com/onlookerzy123456qwq/data_structure_practice_primer/tree/master/4%E6%A0%88%E5%92%8C%E9%98%9F%E5%88%97OJ%E9%A2%98%E9%9B%86%E5%90%88本文所有代码都已上传至gitee,可方便自取。

1.回顾什么是队列和栈

队列其实是先进先出,后进后出,一端负责入,一端负责出,先进入队列的元素,堵在后入队列的元素前面,必须前面的元素出了之后,后面的元素才能出。

请记住队列中元素的出入逻辑,先出的是当前最先入队列的元素。

栈其实是后进先出,先进后出,在同一端进行插入和删除,先入栈的元素,被后入栈的元素压在上面,必须上面后入栈的元素先出,下面这个先入栈的元素才能出。

请记住队列中元素的出入逻辑,先出的是当前最先入队列的元素。

 

2.如何用两个队列实现一个栈

225. 用队列实现栈 - 力扣(LeetCode)

2.1思路讲解

用两个队列模拟实现一个栈,其实就是如何仿生实现,让先入的数据后出,让后入的数据先出。

我们可以始终保持用一个队列装数据(非空队列),一个队列不装数据(空队列)

插入数据的时候,我们在非空队列进行插入数据,当然队列的插入是在队尾的插入

当要获取栈顶数据的时候,我们肯定是要出那个最近一次插入的数据,这样才是栈。我们最近一次插入的那个数据是在当前非空队列的队尾,所以我们直接返回非空队列的QueueBack即可。

当要删除数据的时候,我们栈的删除,是要删除最近一次插入的数据,这才是栈。而最近一次插入的数据,在非空队列的队尾。然而,队列的删除只能从队头删,这时候我们就要借助另一个不装数据的队列了,我们可以把这个非空队列中的元素出列,都依次装到另一个空队列当中直至我这个队列出到只剩下一个元素,这个元素就是我们要找的队尾元素,也即最近一次插入的元素,我们对这个元素删除即可!而且到新队列中的数据仍然保持原来的顺序。

例如非空队列里有1 2 3 4,所以依次插入的顺序是1 2 3 4,要删除的是最近一次插入的4,所以我们依次出1,放入空队列中,出2,放入空队列中,出3,放入空队列中,此时空队列里还是按序存储着1 2 3,非空队列里只剩下4,我们删除即可。

这是从逻辑上进行讲解,展示在代码上,就是我们用两个队列封装一个成栈的各种使用接口。

2.2按照思路实现仿生栈的各接口

在C语言当中,我们必须自己造轮子,我们其实没有一个实际意义上的一个队列,我们首先在OJ当中先实现一个Queue,详情可以看这篇博客:【面向小白】你见过这样讲解队列的吗?(阅此文可学会用纯C手撕一个队列)_yuyulovespicy的博客-CSDN博客

当前这个栈的本质就是两个队列,所以我们定义这个struct myStack类,用两个队列就可以将之代表。

typedef struct {
    Queue _q1;
    Queue _q2;
} MyStack;

然后我们在OJ当中按照我们2.1的思路实现各接口。

2.2.1栈的初始化

任何一个数据结构对象,都要完成初始化,否则你定义出来的指针成员就是野指针,否则你定义出来的int char等类型变量都是随机值。我们当前栈的本质是两个Queue,所以当前栈类的初始化函数就是对队列初始化接口的套壳。

但是注意这里使用创建栈的接口,myStackCreate接口的返回值是MyStack类对象的指针,也就是这个接口的本意,是想让我们把这个栈对象定义在堆区,然后返回这个对象的指针。malloc出来之后,我们就要对其内部的两个队列成员进行初始化

MyStack* myStackCreate() {
    //在堆区创建两个队列
    MyStack* ptmp = (MyStack*)malloc(sizeof(MyStack));
    //分别对两个队列完成初始化
    QueueInit(&(ptmp->_q1));
    QueueInit(&(ptmp->_q2));
    return ptmp;
}

2.2.2栈的销毁

MyStack的本质是两个队列Queue,队列也是在堆区中开辟的空间,比如我们是链式队列,在堆区就有许多的节点需要释放。每次使用完当前栈,都必须要释放堆区空间,否则会导致内存泄漏。

但是注意这里有三块堆区空间。

这里注意释放顺序,我们要首先释放栈指针obj指向的两个队列管理的堆区空间,然后再释放obj指向的Queue对象(即开辟在堆区的指针变量们)。不然释放顺序颠倒的话,我们要释放的是这些开在堆区的指针,我们就无法访问这两个指针了!!!那指针指向的堆区链式队列就丢了!!!


void myStackFree(MyStack* obj) {
    //释放所有在堆区的空间
    //包括MyStack的在堆区的2*2个指针,以及在堆区的链表队列空间
    QueueDestroy(&(obj->_q1));
    QueueDestroy(&(obj->_q2));
    free(obj);
}

2.2.3栈的插入

我们直接对非空队列进行插入即可。

这里介绍一个方法,我们的队列1和队列2,现在谁是空队列,谁是非空队列并不确定,如果我们 if(队列1为空) ......... else(队列2为空) .........,就需要我们写两段代码插入逻辑,会造成代码的冗余,所以我们运用指针假定法,进行判断与修正,最后两个指针,一个指向的就是非空队列,另一个指向的就是空队列。

//obj是栈的指针
void myStackPush(MyStack* obj, int x) {
    //给非空的队列进行插入
    //1.区分空队列/非空队列
    Queue* p_empty_queue = &(obj->_q1);//取出两个队列的指针
    Queue* p_no_empty_queue = &(obj->_q2);
    if(QueueEmpty(p_no_empty_queue))
    {
        p_empty_queue = &(obj->_q2);
        p_no_empty_queue = &(obj->_q1);
    }
    //现在两个指针指向的就是对应的空/非空队列
    //2.给非空队列进行插入
    QueuePush(p_no_empty_queue,x);
}

2.2.4栈的删除

把非空队列出数据到另一个空队列,直至只剩下一个数据,然后删除这个数据即可。

int myStackPop(MyStack* obj) {
    //让非空队列一直出数据到另一个空队列,直至只剩下一个数据
    //0.无有效数据空队列不可以进行删除
    assert(!myStackEmpty(obj));
    //1.区分空队列/非空队列
    Queue* p_empty_queue = &(obj->_q1);//取出两个队列的指针
    Queue* p_no_empty_queue = &(obj->_q2);
    if(QueueEmpty(p_no_empty_queue))
    {
        p_empty_queue = &(obj->_q2);
        p_no_empty_queue = &(obj->_q1);
    }
    //2.非空队列出数据到空队列,直至剩一个,对该个数据进行删除
    while(QueueSize(p_no_empty_queue)>1)
    {
        QueuePush(p_empty_queue,QueueFront(p_no_empty_queue));
        QueuePop(p_no_empty_queue);
    }
    int stacktop = QueueFront(p_no_empty_queue);
    QueuePop(p_no_empty_queue);
    return stacktop;
}

2.2.5 栈的栈顶数据

栈顶就是最近一次插入的数据,非空队列的back队尾就是最近一次插入的数据。

int myStackTop(MyStack* obj) {
    //栈顶就是最近一次插入的数据,即非空队列的back队尾
    //0.无有效数据空队列不可以进行删除
    assert(!myStackEmpty(obj));
    //1.区分空队列/非空队列
    Queue* p_empty_queue = &(obj->_q1);//取出两个队列的指针
    Queue* p_no_empty_queue = &(obj->_q2);
    if(QueueEmpty(p_no_empty_queue))
    {
        p_empty_queue = &(obj->_q2);
        p_no_empty_queue = &(obj->_q1);
    }
    return QueueBack(p_no_empty_queue);
}

2.2.6 判断当前栈是否为空

栈的初始空状态就是,两个队列都是空。

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&(obj->_q1))&&QueueEmpty(&(obj->_q2));
}

3.如何用两个栈实现一个队列

232. 用栈实现队列 - 力扣(LeetCode)

3.1 思路分析

我们先定义两个栈,一个叫PushSt,一个叫PopSt。

思路是类似的,队列是先进的数据,即先插入的数据先出。后进的数据必须等前面的数据出去之后,才能出。

假设我们往栈里依次插入1 2 3 4 5,但是队列要求的是先插入的元素先出,所以说我们下一次应该出的应该是栈底的1,而不是栈顶的5,那怎么倒腾出这个1呢?肯定是要借助另一个栈,我们只要把这个被插入数据的栈,依次出栈(5 4 3 2 1),入栈到另一个栈当中,那此时栈顶的就是1,从栈顶到栈底是1 2 3 4 5(完成了反序)。

 反序之后,此时PopSt栈顶的数据就是最先插入的数据了!!!我们的思路就是这样,在一个PushSt中正序插入,然后倒腾到PopSt当中完成反序,这样在PopSt中,依次出栈的顺序就是先插入的数据-->后插入的数据(如图)。

那反序之后,我们再插入数据是在PushSt当中,还是PopSt当中呢?当然是在PushSt中!一旦插入到PopSt当中,例如我们现在依次把6 7 8插入到PopSt当中,那顺序就全部乱套了!

 

我们要始终维护住PopSt当中的数据,插入的时序上都是早于PushSt当中的所有数据,并且PopSt中元素的出栈顺序就是从最先到最后插入的顺序,这样才满足队列的性质嘛!

所以当插入的时候我们无脑把数据插入到PushSt当中

删除或者取出的数据的时候如果PopSt不为空,那PopSt栈顶的数据就是我们想要的数据如果PopSt为空,那我们就把PushSt当中的数据全部都倒腾到PopSt当中即可。

3.2代码实现

首先我们首先造一个轮子,即手撕一个栈进行使用详情可以参考下面这篇博客:[面向小白]一篇博客带你认识什么是栈以及如何手撕一个栈_yuyulovespicy的博客-CSDN博客

3.2.1 封装该队列

该队列类,是通过两个栈实现的,所以我们封装两个栈成员,即可代表这个队列。

typedef struct {
    //正序栈 负责接收插入的数据 
    //反序栈 负责删除拿出的数据
    Stack _forward_st;
    Stack _reverse_st;
} MyQueue;
/*
forward_st  reverse_st
                1
    7           2
    6           3
    5           4
*/

3.2.2队列的初始化

在堆区创建这个栈对象。

MyQueue* myQueueCreate() {
    //在堆区开辟两个栈
    MyQueue* ptmp = (MyQueue*)malloc(sizeof(MyQueue));
    //对栈进行初始化
    StackInit(&(ptmp->_forward_st));
    StackInit(&(ptmp->_reverse_st));
    return ptmp;
}

3.2.3队列的销毁

清理所有的堆区资源,两个栈实体的资源,以及定义在堆区的队列成员。

void myQueueFree(MyQueue* obj) {
    //释放所有在堆区的空间
    //包括在堆区的两个栈st实体,以及栈管理的顺序表实体
    StackDestroy(&(obj->_reverse_st));
    StackDestroy(&(obj->_forward_st));
    free(obj);
}

3.2.4队列的插入

直接在PushSt,即正序栈_forward_st当中进行插入。

void myQueuePush(MyQueue* obj, int x) {
    //直接在正序栈中进行(栈顶)插入
    StackPush(&(obj->_forward_st),x);
}

3.2.5队列的队头

先插入的元素在PopSt当中,后插入的元素在PushSt当中,并且PopSt当中的数据都是从PushSt反序倒腾来的,从PopSt的栈顶到栈底,其顺序就是满足队列先入先出的性质。所以我们直接获取PopSt的栈顶即可。

当然如果现在PopSt是空,那么我们就要首先把PushSt中的所有数据反序倒腾到PopSt当中。

int myQueuePeek(MyQueue* obj) {
    //获取队头的元素
    //我们直接在反序栈进行返回(栈顶元素)即可
    //如果反序栈是空,就需要先对正序栈中的所有数据倒过来,再进行返回。
    if(StackEmpty(&(obj->_reverse_st)))
    {
        while(!StackEmpty(&(obj->_forward_st)))
        {
            int forward_top = StackTop(&(obj->_forward_st));
            StackPush(&(obj->_reverse_st),forward_top);
            StackPop(&(obj->_forward_st));
        }
    }
    return StackTop(&(obj->_reverse_st));
}

3.2.6队列的删除

就是找到到当前最先插入的数据,即找到队头的数据的基础上,并进行删除即可。

int myQueuePop(MyQueue* obj) {
    //0.首先必须有数据才能删除
    assert(!myQueueEmpty(obj));
    //我们直接在反序栈进行删除(栈顶)即可
    //如果反序栈是空,就需要先对正序栈中的所有数据倒过来,再进行删除。
    if(StackEmpty(&(obj->_reverse_st)))
    {
        while(!StackEmpty(&(obj->_forward_st)))
        {
            int forward_top = StackTop(&(obj->_forward_st));
            StackPush(&(obj->_reverse_st),forward_top);
            StackPop(&(obj->_forward_st));
        }
    }
    //需要返回删除的队头数据
    int front = StackTop(&(obj->_reverse_st));
    StackPop(&(obj->_reverse_st));
    return front;
}

3.2.7队列的判空

很简单,我们所有的有效数据都是存储在两个栈当中的,所以只要两个栈都是空,那么这个队列就是空。

bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(&(obj->_forward_st))&&StackEmpty(&(obj->_reverse_st));
}

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

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

相关文章

梯度下降优化器:SGD -> SGDM -> NAG ->AdaGrad -> AdaDelta -> Adam -> Nadam -> AdamW

目录 1 前言 2 梯度概念 3 一般梯度下降法 4 BGD 5 SGD 6 MBGD 7 Momentum 8 SGDM(SGD with momentum) 9 NAG(Nesterov Accelerated Gradient) 10 AdaGrad 11 RMSProp 12 Adadelta 13 Adam 13 Nadam 14 AdamW 15 Lion(EvoLve…

js 实现 Logo(图片)根据图片后面的图片颜色而变化成相反的颜色【解决logo固定后 会出现与不同板块的颜色相同导致于看不清logo的情况】

效果展示&#xff1a; <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <meta http-equiv"X-UA-Compatible" content"ieedge"><style type"text/css…

进程概念(二)

文章目录进程概念&#xff08;二&#xff09;1. 进程状态1.1 阻塞和挂起状态1.2 进程状态1.2.1 进程查看S状态R状态1.2.2 D状态1.2.3 T状态1.2.4 t状态1.2.5 Z状态(僵尸状态)1.3 孤儿进程2. 环境变量2.1 背景2.2 认识环境变量2.3 获取环境变量2.4 环境变量是什么2.5 认识命令行…

vue:pdf.js使用细节/隐藏按钮/设置、获取当前页码/记录阅读进度/切换语言(国际化)

需求描述 在网页中预览pdf时&#xff0c;希望实现3点需求&#xff1a;1、隐藏一些功能按钮&#xff08;比如下载&#xff09;&#xff1b;2、打开pdf时自动定位到最后浏览的页&#xff08;记录阅读进度&#xff09;&#xff1b;3、实现国际化&#xff08;在代码中更改pdf插件使…

Java面试题-Spring框架

Spring框架 1. BeanFactory和ApplicationContext有何区别 BeanFactory是Spring最底层的接口&#xff0c;是IoC的核心&#xff0c;定义IoC的基本功能。 ​ BeanFactory具有&#xff1a;延迟实例化的特性。在启动的时候&#xff0c;不会实例化Bean&#xff0c;只有有需要从容器…

ESMM的理解和高频面试问题

ESMM的理解首先&#xff0c;理解部分主要是ESMM要解决什么问题&#xff0c;以及解决方案。弱未度过原文的可以查阅原论文。论文地址&#xff1a;https://arxiv.org/pdf/1804.07931.pdf实现代码&#xff1a;https://github.com/PaddlePaddle/PaddleRec/tree/master/models/multi…

2023最新谷粒商城笔记之购物车篇(全文总共13万字,超详细)

购物车 环境搭建 创建购物车项目 第一步、创建gulimall-cart服务&#xff0c;并进行降版本处理 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.8.RELEASE<…

angular框架表格自定义导出,ui组件库为【devExpress by devExtreme】导出插件为exceljs、file-saver

前言 使用的ui组件库为devExtreme注意&#xff1a;如果你没有使用这个组件库&#xff0c;那后续的代码可能对你不适用&#xff01;&#xff01;&#xff01;&#xff0c;因为devExtreme和exceljs是结合着来的 其地址如下&#xff1a; devexpress https://js.devexpress.com/ …

一文速学-Pandas查询索引操作详解+实例代码展示

目录 前言 一、按列表索引查询 查询单值 1.at(单值查询-loc) 2.iat(单值查询-iloc) 3. loc(行/列名索引查询) 4. iloc(行/列索引查询) 二、按条件查询 单条件查询 多条件查询 嵌套筛选 总结 前言 关于一文速学Pandas系列已经将基础部分内容更完&#xff0c;基础部分的…

ASEMI高压MOS管ASE65R330参数,ASE65R330图片

编辑-Z ASEMI高压MOS管ASE65R330参数&#xff1a; 型号&#xff1a;ASE65R330 漏极-源极电压&#xff08;VDS&#xff09;&#xff1a;650V 栅源电压&#xff08;VGS&#xff09;&#xff1a;20V 漏极电流&#xff08;ID&#xff09;&#xff1a;12.5A 功耗&#xff08;P…

[数据结构]:03-栈(C语言实现)

目录 前言 已完成内容 单链表实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-StackCommon.cpp 04-StackFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言代码。使用C引用主要是为了简…

跨境卖家必看的沃尔玛Walmart商家入驻教程

沃尔玛Walmart作为作为全球连锁超市的鼻祖&#xff0c;有不可比拟的知名度。当沃尔玛从线下延伸到线上后&#xff0c;就成为一个自带IP与流量的线上平台&#xff0c;在全世界都拥有数量庞大的消费者群体。所以龙哥就结合自己注册Walmart的过程给大家详细讲解一下。 Walmart卖家…

365智能云打印怎么样?365小票无线订单打印机好用吗?

365智能云打印怎么样&#xff1f;365智能云打印是有赞官方首推的订单小票打印机&#xff0c;荣获2016年有赞最佳硬件服务商。可以实现远程云打印&#xff0c;无需连接电脑&#xff0c;只需通过GPRS流量或者WIFI即可连接&#xff0c;不受地理位置和距离限制。365小票无线订单打印…

1W+企业都在用的数字化管理秘籍,快收藏!

企业数字化&#xff0c;绕不开的话题。 随着国家相继出台各种举措助力中小企业数字化转型&#xff0c;积极推动产业数字化转型&#xff0c;培育数字经济新生态&#xff0c;企业想要谋生存&#xff0c;求发展&#xff0c;必然需要做好数字化转型和管理。 本篇文章想跟大家一起…

【STM32MP157应用编程】2.GPIO输入、输出、中断

目录 GPIO文件 指令操作GPIO 程序操作GPIO 程序说明 程序代码 2_GPIO_4.c 启动交叉编译工具 编译 拷贝到开发板 测试 GPIO文件 在/sys/class/gpio目录下&#xff0c;存放了GPIO的文件。 gpiochipX&#xff1a;当前SoC所包含的GPIO控制器&#xff0c;STM32MP157一共包…

新型开源C2框架:浩劫

研究人员发现了一次针对政府实体的新攻击&#xff0c;攻击者在此期间使用了一种名为浩劫&#xff08;Havoc &#xff09;的新型 C2 框架。 尽管 C2 框架广泛可用&#xff0c;但 Havoc 作为一种先进的后开发框架脱颖而出&#xff0c;可以躲避最新版本的 Windows 11 Defender。 …

一文带你快速初步了解云计算与大数据

目录 &#x1f50d;一、云计算基础 1、云计算的概念、特点、关键技术 2、云计算的分类 3、云计算的部署模式 4、云计算的服务模式&#xff1a;IaaS、PaaS、SaaS分别是什么&#xff0c;具体含义要清楚 5、物联网的概念 6、物联网和云计算、大数据的关系 7、了解云计算的…

Python-生成元组和字典

1.生成元组元组是元素按顺序组合后的产物&#xff0c;元组对象的类型是tuple型含有两个元素的元组成为数据对元组可以包含任意数量和任意类型的元素&#xff0c;其元素总数可以为0、1、2等&#xff0c;并且元素的先后顺序是由意义的。另外&#xff0c;元组中的元素类型没有必要…

open3d最大平面检测,平面分割

1.点云读入 读入文件&#xff08;配套点云下载链接&#xff09; # 读取点云 pcd o3d.io.read_point_cloud("point_cloud_00000.ply")配套点云颜色为白色&#xff0c;open3d的点云显示默认背景为白色&#xff0c;所以将点云颜色更改为黑色 pcd.colors o3d.utilit…

利用 OLE 对象漏洞的 HWP 恶意文件浮出水面

ASEC 分析人员发现了一个利用 OLE 对象的恶意 HWP 文件&#xff0c;尽管其使用了 2020 年就被识别的恶意 URL&#xff0c;但仍然使用了 Flash 漏洞&#xff08;CVE-2018-15982&#xff09;&#xff0c;需要用户谨慎对待。 打开 HWP 文件时会在 %TEMP%文件夹中生成如下文件。攻…