【数据结构和算法】---栈和队列的互相实现

news2025/1/24 1:22:49

目录

  • 一、用栈实现队列
    • 1.1初始化队列
    • 1.2模拟入队列
    • 1.3模拟出队列
    • 1.4取模拟的队列头元素
    • 1.5判断队列是否为空
  • 二、用队列实现栈
    • 2.1初始化栈
    • 2.2模拟出栈
    • 2.3模拟入栈
    • 2.4取模拟的栈顶元素
    • 2.5判读栈是否为空

一、用栈实现队列

具体题目可以参考LeetCode232. 用栈实现队列
首先要想到的是,队列是一种先进先出的结构,而栈是一种先进后出的结构。依此我们可以定义两个栈结构来模拟先进先出,既然要定义两个栈,那么为了方便调用,我们可以将这两个栈结构定义在一个结构体中,如下:

typedef struct {
    ST st1;//栈1
    ST st2;//栈2
} MyQueue;

实现 MyQueue类:

  • void push(int x)将元素 x推到队列的末尾;
  • int pop()从队列的开头移除并返回元素;
  • int peek()返回队列开头的元素;
  • boolean empty()如果队列为空,返回 true;否则,返回 false

1.1初始化队列

我们定义的结构体在主函数外部,为了让每个函数都能用到,那么我们就必须要用malloc来动态开辟空间,这样此结构会被保存在静态区,每次函数调用时便不会销毁此变量,然后再将此结构体中的栈初始化

MyQueue* myQueueCreate() 
{
    MyQueue* queue = (MyQueue*)malloc(sizeof(MyQueue));//动态开辟结构体变量
    //注意初始化栈的参数为地址
    StackInit(&queue->st1);//初始化栈1
    StackInit(&queue->st2);//初始化栈2
    return queue;
}

1.2模拟入队列

我们可以将栈1作为存数据的栈,那么每次入队列操作就是进栈操作(StackPush(&obj->st1, x);)。

void myQueuePush(MyQueue* obj, int x) 
{
    assert(obj);
    StackPush(&obj->st1, x);
}

1.3模拟出队列

  1. 思路1:
    如果我们用栈1obj->st1来存放数据,在模拟出队列时我们首先要断言栈1不为空,那么当栈1不为空且我们需要出队列头元素时。此时就需要栈2obj->st2来暂存数据,即我们将栈1除栈底的全部元素都出栈并入栈到栈2obj->st2,然后再出栈1最后的元素并返回,这样就模拟了先入先出性质。还需要注意的是在返回最后一个元素前还需要再将所有数据从栈2再入到栈1。逻辑如下:

在这里插入图片描述

  1. 思路2:
    栈1用来存数据,栈2用来出数据。 那么为什么栈2的元素可以直接出呢?当我们需要模拟出队列时,我们可以先将栈1中所以元素出栈并入栈到栈2,这样一来栈2中的top就相当于队列头元素。每次从栈2中出元素时要先判断栈2中是否有元素,若没有,就将栈1中的元素出栈并入栈到栈2中。大致逻辑如下:

在这里插入图片描述

与思路一相比较,思路二栈2无需重新入栈1,还可继续模拟出队列。只能说两种思路各有好处,下列代码实现使用的是思路一:

int myQueuePop(MyQueue* obj) 
{
    assert(obj);
    assert(StackSize(&obj->st1) != 0);//栈1不为空
    ST* empty = &obj->st2;//栈2为空
    ST* noempty = &obj->st1;//栈1不为空
    //将栈1除栈底的所有元素出栈并入栈到栈2
    while(StackSize(noempty) > 1)
    {
        StackPush(empty,StackTop(noempty));
        StackPop(noempty);
    }
    //找到队头
    int ret = StackTop(noempty);
    StackPop(noempty);
    //重新入栈1
    while(StackSize(empty) > 0)
    {
        StackPush(noempty,StackTop(empty));
        StackPop(empty);
    }
    return ret;
}

1.4取模拟的队列头元素

此函数实现与1.3模拟出队列方法相似,就不多介绍了,如下:

int myQueuePeek(MyQueue* obj)
{
    assert(obj);
    ST* empty = &obj->st2;
    ST* noempty = &obj->st1;
    //将栈1除栈底的所有元素出栈并入栈到栈2
    while(StackSize(noempty) > 1)
    {
        StackPush(empty,StackTop(noempty));
        StackPop(noempty);
    }
    //找到队头
    int ret = StackTop(noempty);
    StackPush(empty,ret);
    StackPop(noempty);
    //重新入栈1
    while(StackSize(empty) > 0)
    {
        StackPush(noempty,StackTop(empty));
        StackPop(empty);
    }
    return ret;
}

1.5判断队列是否为空

依据上面思路,因为栈1是用来存数据的,所以当栈1为空时就代表我们模拟的队列为空。

bool myQueueEmpty(MyQueue* obj) 
{
    assert(obj);
    return StackEmpty(&obj->st1);
}

二、用队列实现栈

具体题目可以参考LeetCode225. 用队列实现栈
与用栈实现队列相似,我们同样需要两个队列来模拟实现栈,且关键在于还原队列先入先出的性质,依此性质来实现函数。既然这样我们可以如下定义结构体:

typedef struct 
{
    Queue* q1;//队列1
    Queue* q2;//队列2
} MyStack;

我们可以看到与模拟队列不同的是,模拟栈的结构体中存放的是两个结构体指针,这与队列的实现方法有关。因为我们用的队列是用链表实现的,所以其每个节点都是组成队列的一部分,且我们应该通过指针将他们一个个都连接起来(即通过指针来寻找节点)。
至于用栈实现队列问题中的结构体我们存放的是两个关于栈的结构体,是因为我们所使用的栈使用数组来实现的,这样一来我们操作的就是栈结构体中某一个元素(即动态开辟的数组)。当然在我们也可以放两个栈结构体指针,只不过在下面初始化队列时(myQueueCreate() )我们需要额外malloc动态开辟栈结构大小的空间,然后将指针指向该空间的地址。
实现 MyStack类:

  • void push(int x)将元素 x压入栈顶;
  • int pop()移除并返回栈顶元素;
  • int top()返回栈顶元素;
  • boolean empty()如果栈是空的,返回 true;否则,返回 false

2.1初始化栈

malloc()动态开辟栈结构体没什么问题,与模拟队列相似。但为什么还要给结构体中的两个队列结构体指针动态开辟空间呢?这样不就违背了我们上面探讨的问题了吗?其实不然,这里的两个结构体指针事实上指向的是存放队列头指针和尾指针的结构体,如下:

typedef struct Queue
{
	QNode* phead;//队列头指针
	QNode* ptail;//队列尾指针
	int size;//长度
}Queue;

这样一来,基本每个函数都需要用到此结构体,那么我们就必须malloc开辟来增加作用域和生命周期。 最后再初始化这两个存放头/尾指针的结构体,并返回用来模拟栈的结构体地址。

MyStack* myStackCreate() 
{
    MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
	pst->q1 = (Queue*)malloc(sizeof(Queue));
	pst->q2 = (Queue*)malloc(sizeof(Queue));
    QueueInit(pst->q1);
    QueueInit(pst->q2);
    return pst;
}

2.2模拟出栈

与模拟出队列不同的是,这里用来模拟出栈的两个队列都可以用来出栈和入栈,具体方法如下:
为了还原栈先入后出的性质,我们可以先找到不为空的队列(因为两个队列都有可能有数据,但不同时有),然后将有数据的队列(noempty)除队尾的一个节点全都出队列并入队列到无数据的队列(empty,这样一来就找到了尾节点(模拟的栈顶)。还需要注意的是,此时我们无需再将数据重新入到noempty 逻辑大致如下:

在这里插入图片描述

int myStackPop(MyStack* obj) 
{
    //先假设队列1为空
    Queue* empty = obj->q1;
    Queue* noempty = obj->q2;
    //纠正
    if(QueueEmpty(obj->q2))
    {
        empty = obj->q2;
        noempty = obj->q1;
    }
    //noempty出,并入到empty
    while(QueueSize(noempty) > 1)
    {
        int cmp = QueueFront(noempty);
        QueuePop(noempty);
        QueuePush(empty, cmp);
    }
    //取到模拟的栈顶元素
	int ret = QueueFront(noempty);
	QueuePop(noempty);
    return ret;
}

2.3模拟入栈

依据上面的方法,我们是要将数据入到不为空的队列,简单的if语句便可完成筛选。

void myStackPush(MyStack* obj, int x) 
{
    assert(obj);
    if(!QueueEmpty(obj->q1))
    {
        QueuePush(obj->q1, x);
    }
    else
    {
        QueuePush(obj->q2, x);
    }
}

2.4取模拟的栈顶元素

同样我们需要找到不为空的那个队列,且事实上队列尾指针指向的那个节点就是模拟的栈的栈顶,我们只需返回此元素即可。

int myStackTop(MyStack* obj) 
{
    assert(obj);
    //找不为空的队列
    if(!QueueEmpty(obj->q1))
        return QueueBack(obj->q1);
    else
        return QueueBack(obj->q2);
}

2.5判读栈是否为空

当两个队列都没有数据时,那么模拟的栈就是空栈。

bool myStackEmpty(MyStack* obj) 
{
    assert(obj);
    return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

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

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

相关文章

目标检测应用场景—数据集【NO.24】行人车辆检测数据集2

写在前面:数据集对应应用场景,不同的应用场景有不同的检测难点以及对应改进方法,本系列整理汇总领域内的数据集,方便大家下载数据集,若无法下载可关注后私信领取。关注免费领取整理好的数据集资料!今天分享…

Neural-Chat-7B:Intel如何塑造下一代对话AI

引言 在人工智能的领域中,对话模型的进步正改变我们与技术的互动方式。Intel近期推出的Neural-Chat-7B模型,不仅标志着对话AI的一大步前进,更预示着未来人机交流的新方向。本文将深入探索这一模型的核心特性、训练过程及其在实际应用中的潜力…

【内存泄漏】Malloc Debug 和 libmenunreacbale 原理介绍

内存泄漏检测原理介绍 malloc debug 原理介绍 分为初始化和内存泄漏检测两个阶段介绍。 初始化阶段 整体流程如下图 libc 初始化时通过 __libc_init_malloc 函数调用 MallocInitImpl 来初始化 memory allocation framework。 // malloc_common_dynamic.cpp static const…

2024应届大学生,为云计算高薪岗位做好准备了吗?

云计算正处于快速发展阶段,对于企业和个人来说,云计算提供了方便、灵活和智能的解决方案,对各行各业都有着重要的影响和推动作用。 随着云计算新市场、新业务、新应用的不断出现,人力需求迅猛。国家相继出台一系列政策大力扶持云…

结构型模式 | 适配器模式

一、适配器模式 1、原理 适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式主要分为三类:类适配器模式、对象适配器模式、接口…

什么是Web 3.0以及为什么它很重要【译文】

作者:马克斯-默施和理查德-穆尔黑德 什么技术使30多亿人每天80%的清醒时间受益?就是Web 2.0。 Web 2.0是OReilly等人在1999年至2004年间提出的,它将世界从为信息消费而设计、由昂贵的服务器提供的静态桌面网页转向互动体验和用户生成的内容&a…

革命性突破:Great River推出XL高速ARINC 818传感器测试卡

Great River Technology荣幸地宣布,与RVS(远程视觉系统)2.0平台合作推出的XL高速ARINC 818传感器测试卡正式亮相。这款开创性的测试卡在柯林斯航空电子公司(RTX业务部)和波音公司开发和测试RVS 2.0系统中发挥了重要作用…

【Spring Security】认证密码加密Token令牌CSRF的使用详解

🎉🎉欢迎来到我的CSDN主页!🎉🎉 🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚 🌟推荐给大家我的专栏《Spring Security》。🎯🎯 …

P3375 【模板】KMP

【模板】KMP 题目描述 给出两个字符串 s 1 s_1 s1​ 和 s 2 s_2 s2​,若 s 1 s_1 s1​ 的区间 [ l , r ] [l, r] [l,r] 子串与 s 2 s_2 s2​ 完全相同,则称 s 2 s_2 s2​ 在 s 1 s_1 s1​ 中出现了,其出现位置为 l l l。 现在请你求…

关于使用libnet时性能下降的问题分析

Libnet是一个用于构建和注入网络数据包的便携式框架。它提供了在IP层和链路层创建数据包的功能,以及一系列辅助和补充功能。Libnet非常适合编写网络工具和网络测试代码。一些使用libnet的项目包括arping、ettercap、ipguard、isic、nemesis、packit、tcptraceroute和…

Ubuntu 常用命令之 scp 命令用法介绍

📑Linux/Ubuntu 常用命令归类整理 SCP(Secure Copy)是一种基于SSH(Secure Shell)的文件传输协议,它可以在本地和远程主机之间安全地复制文件。在Ubuntu系统下,我们可以使用scp命令来实现这个功…

异步消息原理

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 在日常开发中&#xff…

什么是递归

概述 递归是一种解决问题的方法,它通过将一个问题分解为同样类型的子问题来解决问题。在递归中,函数会调用自身,并向下逐步解决问题,直到到达问题的基本情况。 递归的示例可以是计算一个数的阶乘。阶乘的定义是对于正整数n&…

React学习计划-React16--React基础(五)脚手架创建项目、todoList案例、配置代理、消息订阅与发布

一、使用脚手架create-react-app创建项目 react脚手架 xxx脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目 包含了所有需要的配置(语法检查、jsx编译、devServe…)下载好了所有相关的依赖可以直接运行一个简单的效果 react提供了一个…

Flink快速部署集群,体验炸了!

📢📢📢📣📣📣 哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】!😜&am…

【Element】el-table 使用 el-table-infinite-scroll 插件实现滚动加载

虽然 el 官方提供了 Infinite Scroll 无限滚动 组件 但是却不支持 el-table 组件,这就很难受了,还好已经有大佬写好了插件,并且支持 element-plus/infinite-scroll 组件的所有选项。 el-table-infinite-scroll el-table-infinite-scroll 看…

Linux目录切换相关命令@cd/pwd

目录 基础指令 cd命令原型命令的搭配以及效果命令本身cd cd 指定目录 基础指令 pwd命令原型pwd 总结: 基础指令 cd cd 取自英文 Change Directory 的首字母组成。 英文的中文翻译为:更改目录。 很明显该指令是用来更改目录的。 命令原型 cd [Linux路径…

企业“数据入表”之政策及业务模式解读

2023年8月21日,财政部重磅发布了《企业数据资源相关会计处理暂行规定》(以下简称“暂行规定”),该规定将于2024年1月1日正式施行。 “暂行规定”发布后,引起全社会的广泛关注,关注的焦点集中在数据入表概念…

Unity中Shader旋转矩阵(四维旋转矩阵)

文章目录 前言一、围绕X轴旋转1、可以使用上篇文章中,同样的方法推导得出围绕X轴旋转的点阵。2、求M~rotate~ 二、围绕Y轴旋转1、可以使用上篇文章中,同样的方法推导得出围绕Y轴旋转的点阵。2、求M~rotate~ 三、围绕Z轴旋转1、可以使用上篇文章中&#x…

【数据结构之单链表】

数据结构学习笔记---003 数据结构之单链表1、什么是单链表?1.1、概念及结构 2、单链表接口的实现2.1、单链表的SList.h2.1.1、定义单链表的结点存储结构2.1.2、声明单链表各个接口的函数 2.2、单链表的SList.c2.2.1、遍历打印链表2.2.2、销毁单链表2.2.3、打印单链表元素2.2.4…