【数据结构与算法】用栈实现队列

文章目录

  • 😻前言
  • 如何用栈实现队列?
  • 用栈实现队列
  • 整体的实现代码
  • 😼写在最后

😻前言

😝上一章我们用队列实现了一个栈(-> 传送门 <-),而这一章就带大家用栈实现一个队列。

😜用队列实现一个栈,用的是两个队列,其出栈操作可以说是最麻烦的一步,它通过倒数据的方式最后完成出栈。而用栈实现一个队列,很明显也是需要两个栈来完成的,其出队操作其实也与倒数据的方式有关,不过两个实现方法有所不同。

🤪用队列实现栈,是通过队列的 先进先出 的性质来实现栈的 后进先出 的性质;而用栈实现队列,是通过栈的 后进先出 的性质来实现队列的 先进先出 的性质,大家别弄混淆了。


如何用栈实现队列?

  • 那么如何用栈实现队列呢?肯定是需要两个栈来完成的。

  • 用两个栈,每一个栈都是数据后进先出,我们仔细思考队列的先进先出这一性质,如何来操作这两个栈才能达到这样的一个性质?

  • 我们可以这样操作:规定一个栈为(入数据的栈),一个栈为(出数据的栈)。一开始两个栈都为空,当我们要入队列的时候,只需要在那个入数据的栈中将数据入栈即可。当我们要出队列的时候,由于队列的性质是先进先出,这时单凭那个入数据的栈实现不了此性质的功能,因此那个出数据的栈(当前为空栈)就起作用了,具体操作为:将那个入数据的栈里面的数据依次出栈,并入入出数据的栈中,这样,最先进入入数据的栈的数据就到了出数据的栈的栈顶位置,随后在此栈出栈即可。整个过程的确实现了队列先进先出这一性质的功能,这也是最好的解决方案了 。当然,后面如果一直出队列直到出数据的栈里没有数据可出了,而此时还想执行出队列操作,那么再向入数据的栈中倒数据过来就可以了(再次执行上面的操作)。注意:无论如何,入队列的数据始终都是 入入 入数据的栈中。

在这里插入图片描述

  • 此外,用栈实现一个队列,还需要有判空,取队头元素,队列的销毁这些功能,不过这些都是小问题,我们可以巧用轮子 (轮子就是我们提前已经实现好的栈的一系列功能) 来灵活解决这些问题。

用栈实现队列

  • 这里我们直接以题目的方式来实现,题目链接:-> 传送门 <-

题目描述:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

该题提供的需要我们实现的接口:

typedef struct {

} MyQueue;


MyQueue* myQueueCreate() {

}

void myQueuePush(MyQueue* obj, int x) {

}

int myQueuePop(MyQueue* obj) {

}

int myQueuePeek(MyQueue* obj) {

}

bool myQueueEmpty(MyQueue* obj) {

}

void myQueueFree(MyQueue* obj) {

}
  • 由于这里我们用C语言实现,因此需要 “造轮子”,也就是将之前实现过的栈拷贝过去。

接下来,就是对队列的一系列功能接口的实现了:

1.

  • 首先当然是造轮子,有了轮子,我们对栈的一系列操作,只需要调用我们已经实现好的函数接口即可。

  • 我们将之前写的栈直接拷贝过来,拷贝的代码如下:

typedef int STDataType;
typedef struct stack
{
	STDataType* a;
	int capacity;
	int top;
}stack;

// 初始化
void STInit(stack* pt);
// 入栈
void STPush(stack* pt, STDataType x);
// 出栈
void STPop(stack* pt);
// 判空
bool STEmpty(stack* pt);
// 取栈顶元素
STDataType STTop(stack* pt);
// 栈的元素个数
int STSize(stack* pt);
// 销毁栈
void STDestroy(stack* pt);

void STInit(stack* pt)
{
	assert(pt);
	pt->a = NULL;
	pt->capacity = 0;
	pt->top = 0;
}

void STPush(stack* pt, STDataType x)
{
	assert(pt);
	if (pt->top == pt->capacity)
	{
		int newcapacity = pt->capacity == 0 ? 4 : pt->capacity * 2;
		STDataType* tmp = realloc(pt->a, sizeof(STDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		pt->a = tmp;
		pt->capacity = newcapacity;
	}
	pt->a[pt->top++] = x;
}
void STPop(stack* pt)
{
	assert(pt && !STEmpty(pt));
	pt->top--;
}

bool STEmpty(stack* pt)
{
	assert(pt);

	return pt->top == 0;
}
STDataType STTop(stack* pt)
{
	assert(pt && !STEmpty(pt));
	return pt->a[pt->top - 1];
}

int STSize(stack* pt)
{
	assert(pt);
	return pt->top;
}

void STDestroy(stack* pt)
{
	assert(pt);

	free(pt->a);
	pt->capacity = 0;
	pt->top = 0;
}

2.

  • 有了轮子之后,就是对队列的结构体的创建了,由于队列是由两个栈实现的(一个为入数据的栈,一个为出数据的栈),因此队列的结构体的成员也是两个栈:

相关代码实现:

// 匿名结构体
typedef struct {
	// 入数据的栈
    stack pushst;
    // 出数据的栈
    stack popst;
} MyQueue;

3.

  • 然后是创建一个队列,就是开辟一个队列的空间,其间包含对队列里的两个栈的初始化操作。

相关代码实现:

MyQueue* myQueueCreate() {
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
    assert(obj);
	
	// 调用栈自有的初始化函数
    STInit(&obj->pushst);
    STInit(&obj->popst);

    return obj;
}

4.

  • 接着就是对入队列操作的实现。

  • 前面说过,无论如何,只需要在入数据的栈中入栈即可。

相关代码实现:

void myQueuePush(MyQueue* obj, int x) {
	// 调用自己的入栈函数
    STPush(&obj->pushst, x);
}

5.

  • 再接着就是最复杂的出队列操作。

  • 由前面的介绍,我们已经知道了思路,而现在最主要的,就是我们还需要对出数据的栈进行判空,如果出数据的栈为空,就需要在入数据的栈里倒数据过来,然后再出栈。如果不为空,直接出栈即可。

相关代码实现:

注意: 由于该函数功能与下面的返回队列开头的元素的函数功能类似,只是一个要将数据干掉,一个不干,因此,这里直接复用返回队列开头的元素的函数功能,最后只要将数据删除即可。

int myQueuePop(MyQueue* obj) {
    // assert(!myQueueEmpty(obj));

	   // 首先判断是否是空,是否需要倒数据
    // if (STEmpty(&obj->popst))
    // {
    //     // 为空就倒数据
    //     while (!STEmpty(&obj->pushst))
    //     {
    //         int top = STTop(&obj->pushst);
    //         STPop(&obj->pushst);
    //         STPush(&obj->popst);
    //     }
    // }

    // int top = STTop(&obj->popst);
    // STPop(&obj->popst);
    // return top;

    int top = myQueuePeek(obj);
    // 多了一步删除操作
    STPop(&obj->popst);

    return top;
}

6.

  • 当然还有获取队头数据的功能,也就是返回队列开头的元素

  • 在上面出队列部分已经说明思路,只是这里不需要删除那个队头数据。

相关代码实现:

int myQueuePeek(MyQueue* obj) {
    assert(!myQueueEmpty(obj));

    if (STEmpty(&obj->popst))
    {
        while (!STEmpty(&obj->pushst))
        {
            int top = STTop(&obj->pushst);
            STPop(&obj->pushst);
            STPush(&obj->popst, top);
        }
    }

    return STTop(&obj->popst);
}

7.

  • 论队列的功能怎么能少得了判空呢。

  • 对于该队列的判空,我们实际上只需要判断那两个栈是否为空即可。

相关代码实现:

bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}

8.

  • 最后就是队列的销毁了。

  • 将两个栈销毁(调用自己的销毁函数),然后将队列销毁即可。

相关代码实现:

void myQueueFree(MyQueue* obj) {
	// 调用自己的销毁函数
    STDestroy(&obj->pushst);
    STDestroy(&obj->popst);
    
    // 销毁队列
    free(obj);
}

整体的实现代码

typedef int STDataType;
typedef struct stack
{
	STDataType* a;
	int capacity;
	int top;
}stack;

// 初始化
void STInit(stack* pt);
// 入栈
void STPush(stack* pt, STDataType x);
// 出栈
void STPop(stack* pt);
// 判空
bool STEmpty(stack* pt);
// 取栈顶元素
STDataType STTop(stack* pt);
// 栈的元素个数
int STSize(stack* pt);
// 销毁栈
void STDestroy(stack* pt);

void STInit(stack* pt)
{
	assert(pt);
	pt->a = NULL;
	pt->capacity = 0;
	pt->top = 0;
}

void STPush(stack* pt, STDataType x)
{
	assert(pt);
	if (pt->top == pt->capacity)
	{
		int newcapacity = pt->capacity == 0 ? 4 : pt->capacity * 2;
		STDataType* tmp = realloc(pt->a, sizeof(STDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		pt->a = tmp;
		pt->capacity = newcapacity;
	}
	pt->a[pt->top++] = x;
}
void STPop(stack* pt)
{
	assert(pt && !STEmpty(pt));
	pt->top--;
}

bool STEmpty(stack* pt)
{
	assert(pt);

	return pt->top == 0;
}
STDataType STTop(stack* pt)
{
	assert(pt && !STEmpty(pt));
	return pt->a[pt->top - 1];
}

int STSize(stack* pt)
{
	assert(pt);
	return pt->top;
}

void STDestroy(stack* pt)
{
	assert(pt);

	free(pt->a);
	pt->capacity = 0;
	pt->top = 0;
}

typedef struct {
    stack pushst;
    stack popst;
} MyQueue;

int myQueuePeek(MyQueue* obj);
bool myQueueEmpty(MyQueue* obj);

MyQueue* myQueueCreate() {
    MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
    assert(obj);

    STInit(&obj->pushst);
    STInit(&obj->popst);

    return obj;
}

v oid myQueuePush(MyQueue* obj, int x) {
    STPush(&obj->pushst, x);
}

int myQueuePop(MyQueue* obj) {
    // assert(!myQueueEmpty(obj));

    // if (STEmpty(&obj->popst))
    // {
    //     while (!STEmpty(&obj->pushst))
    //     {
    //         int top = STTop(&obj->pushst);
    //         STPop(&obj->pushst);
    //         STPush(&obj->popst);
    //     }
    // }

    // int top = STTop(&obj->popst);
    // STPop(&obj->popst);
    // return top;

    int top = myQueuePeek(obj);
    STPop(&obj->popst);

    return top;
}

int myQueuePeek(MyQueue* obj) {
    assert(!myQueueEmpty(obj));

    if (STEmpty(&obj->popst))
    {
        while (!STEmpty(&obj->pushst))
        {
            int top = STTop(&obj->pushst);
            STPop(&obj->pushst);
            STPush(&obj->popst, top);
        }
    }

    return STTop(&obj->popst);
}

bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}

void myQueueFree(MyQueue* obj) {
    STDestroy(&obj->pushst);
    STDestroy(&obj->popst);
    free(obj);
}

😼写在最后

💝学会了用队列实现栈,也学会了用栈实现队列,回想一下,还是挺简单的,下一篇将带大家设计一个循环队列,难度会有些上升噢,大家打起精神来!!!
❤️‍🔥后续将会持续输出有关数据结构与算法的文章,你们的支持就是我写作的最大动力!

感谢阅读本小白的博客,错误的地方请严厉指出噢~

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

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

相关文章

< 每日算法:在排序数组中查找元素的第一个和最后一个位置 >

每日算法 - JavaScript解析&#xff1a;在排序数组中查找元素的第一个和最后一个位置 一、任务描述&#xff1a;> 示例 1> 示例 2> 示例 3二、题意解析三、解决方案&#xff1a;往期内容 &#x1f4a8;一、任务描述&#xff1a; 给你一个按照非递减顺序排列的整数数组…

C++基础算法③——排序算法(选择、冒泡附完整代码)

排序算法 1、选择排序 2、冒泡排序 1、选择排序 基本思想&#xff1a;从头至尾扫描序列&#xff0c;每一趟从待排序元素中找出最小(最大)的一个元素值&#xff0c;然后与第一个元素交换值&#xff0c;接着从剩下的元素中继续这种选择和交换方式&#xff0c;最终得到一个有序…

冲击蓝桥杯-时间问题(必考)

目录 前言&#xff1a; 一、时间问题 二、使用步骤 1、考察小时&#xff0c;分以及秒的使用、 2、判断日期是否合法 3、遍历日期 4、推算星期几 总结 前言&#xff1a; 时间问题可以说是蓝桥杯&#xff0c;最喜欢考的问题了,因为时间问题不涉及到算法和一些复杂的知识&#xf…

第十四届蓝桥杯三月真题刷题训练——第 18 天

目录 第 1 题&#xff1a;排列字母 问题描述 运行限制 代码&#xff1a; 第 2 题&#xff1a;GCD_数论 问题描述 输入格式 输出格式 样例输入 样例输出 评测用例规模与约定 运行限制 第 3 题&#xff1a;选数异或 第 4 题&#xff1a;背包与魔法 第 1 题&#x…

1649_Excel中删除重复的数据

全部学习汇总&#xff1a; GreyZhang/windows_skills: some skills when using windows system. (github.com) 长久时间的开发工作性质导致我对Windows生态下的很多工具没有一个深度的掌握。有时候&#xff0c;别说深度&#xff0c;可能最为浅显的操作都是不熟悉的。这个不仅仅…

JVM学习.02 内存分配和回收策略

1、前言《JVM学习.01 内存模型》篇讲述了JVM的内存布局&#xff0c;其中每个区域是作用&#xff0c;以及创建实例对象的时候内存区域的工作流程。上文还讲到了关于对象存货后&#xff0c;会被回收清理的过程。今天这里就着重讲一下对象实例是如何被清理回收的&#xff0c;以及清…

5.方法(最全C#方法攻略)

目录 5.1 方法的结构 5.2 方法体内部的代码执行 5.3.1 类型推断和Var关键字 5.3.2 嵌套块中的本地变量 5.4 本地常量 5.5 控制流 5.6 方法调用 5.7 返回值 5.8 返回语句和void 方法 5.9 参数 5.9.1 形参 5.9.2 实参 位置参数示例 5.10 值参数 5.11 引用参数 5.12…

【vm虚拟机】vmware虚拟机下载安装

vmware虚拟机下载安装&#x1f6a9; vmware虚拟机下载&#x1f6a9; 安装虚拟机程序&#x1f6a9; 创建一个CentOS虚拟机&#x1f6a9; 异常情况&#x1f6a9; vmware虚拟机下载 vmware官网下载地址 &#x1f6a9; 安装虚拟机程序 双击安装包exe程序&#xff0c;无脑下一步即…

来到CSDN的一些感想

之所以会写下今天这篇博客&#xff0c;是因为心中实在是有很多话想说&#xff01;&#xff01;&#xff01; 认识我的人应该都知道&#xff0c;我是才来CSDN不久的&#xff0c;也可以很清楚地看见我的码龄&#xff0c;直到今天&#xff1a;清清楚楚地写着&#xff1a;134天&…

完美日记母公司再度携手中国妇基会,以“创美人生”助力女性成长

撰稿 | 多客 来源 | 贝多财经 当春时节&#xff0c;梦想花开。和煦的三月暖阳&#xff0c;唤醒的不止是满城春意&#xff0c;更有逸仙电商“创美人生”公益项目播撒的一份希望。 3月8日“国际妇女节”当日&#xff0c;为积极响应我国促进共同富裕的政策倡导&#xff0c;助力相…

C语言--自定义类型详解

目录结构体结构体的声明特殊的声明结构的自引用typedef的使用结构体变量的定义和初始化结构体的内存对齐为什么存在内存对齐&#xff1f;修改默认对齐数结构体传参位段位段的内存分配位段的跨平台问题枚举联合联合类型的定义联合在内存中开辟空间联合大小的计算结构体 结构体的…

Linux之磁盘分区、挂载

文章目录一、Linux分区●原理介绍●硬盘说明查看所有设备挂载情况挂载的经典案例二、磁盘情况查询基本语法应用实例磁盘情况-工作实用指令一、Linux分区 ●原理介绍 Linux来说无论有几个分区&#xff0c;分给哪一目录使用&#xff0c;它归根结底就只有一个根目录&#xff0c;…

可编程线性直流电源的特性有哪些?

可编程线性直流电源是一种高性能、高精度的电源设备&#xff0c;其主要特性包括以下几点&#xff1a;1、高稳定性&#xff1a;可编程线性直流电源具有极高的输出稳定性&#xff0c;能够保证输出电压、电流和功率的精度和稳定性。通常来说&#xff0c;稳定性能够达到0.01%或更高…

Linux的诞生过程

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。个人主页&#xff1a;小李会科技的…

Android Lancet Aop 字节编码修复7.1系统Toast问题(WindowManager$BadTokenException)

近期在Bugly上出现7.1以下设备上出现大量BadTokenException&#xff1a; android.view.WindowManager$BadTokenExceptionUnable to add window -- token android.os.BinderProxy6c0415d is not valid; is your activity running?报错堆栈&#xff0c;如下所示&#xff1a; …

数据分析师CDA认证 Level Ⅰ笔记

**黑色字体部分为考纲&#xff0c;蓝色字体部分为笔记&#xff0c;仅供参考 PART 1 数据分析概念与职业操守 1、数据分析概念、方法论、角色 【领会】 数据分析基本概念&#xff08;数据分析、数据挖掘、大数据&#xff09; 数据分析目的及其意义 数据分析方法与流程 数据分析的…

【网络安全工程师】从零基础到进阶,看这一篇就够了

学前感言 1.这是一条需要坚持的道路&#xff0c;如果你只有三分钟的热情那么可以放弃往下看了。 2.多练多想&#xff0c;不要离开了教程什么都不会&#xff0c;最好看完教程自己独立完成技术方面的开发。 3.有问题多google,baidu…我们往往都遇不到好心的大神&#xff0c;谁…

【Leetcode】队列实现栈和栈实现队列

目录 一.【Leetcode225】队列实现栈 1.链接 2.题目再现 3.解法 二.【Leetcode232】栈实现队列 1.链接 2.题目再现 3.解法 一.【Leetcode225】队列实现栈 1.链接 队列实现栈 2.题目再现 3.解法 这道题给了我们两个队列&#xff0c;要求去实现栈&#xff1b; 首先&…

8大核心语句,带你深入python

人生苦短 我用python 又来给大家整点好东西啦~ 咱就直接开练噜&#xff01;内含大量代码配合讲解 python 安装包资料:点击此处跳转文末名片获取 1. for - else 什么&#xff1f;不是 if 和 else 才是原配吗&#xff1f; No&#xff0c;你可能不知道&#xff0c; else 是个…

Cache的地址结构,tag到底与Cache什么关系,Cache容量与总容量,Cache行长,Cache字地址?

目录.Cache映射的问题一.Cache的三种映射重点&#xff1a;那么我说1.直接映射2.全相联映射3.组相联映射4.总结三种映射二.Cache的三个字眼(例题)1.Cache字地址多少位&#xff08;字地址即按字编址&#xff09;2.Cache容量与总容量3.Cache行长一.Cache的三种映射 重点&#xff…