【栈和队列面试题】用栈实现队列(动图解析更清晰)

简介: 【栈和队列面试题】用栈实现队列(动图解析更清晰)

leetcode 232.用栈实现队列


前言:用两个栈实现一个队列,模拟实现队列的功能

💥🎈个人主页:Dream_Chaser~ 🎈💥

✨✨刷题专栏:http://t.csdn.cn/UlvTc

💨💨本篇内容:力扣上栈与队列面试题目

c05035f62cb048418b3dbce7c7e6f2b1.gif

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

d79642a64142406dab373e2b34382a23.png

📌结构体类型的声明(MyQueue)

       使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈(pushst),一个输出栈(popst)

typedef struct {
   ST pushst;//输入栈
   ST popst;//输出栈
} MyQueue;//自定义队列结构体


1️⃣队列的初始化(myQueueCreate)

图解:

d4fa8a490a6041d6903a52382746553b.png

代码实现:

MyQueue* myQueueCreate() {
    //使用malloc函数为MyQueue结构体分配内存空间,定义一个MyQueue*  的指针指向这块空间
    MyQueue* obj =(MyQueue*)malloc(sizeof(MyQueue));
    // 若分配失败
        if(obj == NULL)
    {//则打印错误信息
      perror("malloc fail");
      return NULL;//返回NULL
    }
    STInit(&obj->pushst);//初始化输入栈
    STInit(&obj->popst);//初始化输出栈
    return obj;//返回指向这个结构体的指针
}


2️⃣元素入队列(myQueuePush)

图解:在push数据的时候,只要数据放进输入栈就好

77be90d66ea241458f4a0652a241b231.gif

void myQueuePush(MyQueue* obj, int x) {
    STPush(&obj->pushst , x);//往队列导入元素 
}

3️⃣获得队首元素(myQueuePeek)

       假设我们需要在正常的队列中获取头部元素,那么应该怎么在这个自定义的队列中获得呢?

a310c64c666142fe8b0d7a5d43fbc7ce.png

 那么就应该想清楚(Output stack)输出栈出栈顺序由下图可以看出,输入栈(Input stack)出栈顺序是:3 2 1,那么(Output stack)输出栈入栈顺序是: 3 2 1,自然的,(Output stack)输出栈出栈顺序就应该为1 2 3,那么(Output stack)输出栈的这个元素1就是队列的队首元素

71b7587c99b441c4ad6d8489cbccc6e7.png

    这个函数的功能,先判断输出栈为空的前提下,循环条件是输入栈不为空,STPush()的功能就是把输入栈的元素,一个一个导入到输出栈再一个一个把输入栈的元素弹出直到输入栈为空栈时结束循环,返回此时的输出栈栈顶元素

int myQueuePeek(MyQueue* obj) {
    if(STEmpty(&obj->popst))//若输出栈为空
    { 
            //结束条件是把输入栈元素全部挪动到输出栈为止
      while(!STEmpty(&obj->pushst))//输入栈不为空
      {   //获得输入栈头部元素,挪动到输出栈
        STPush(&obj->popst,STTop(&obj->pushst));
        STPop(&obj->pushst);//弹出输入栈元素
      }
    }
        //返回此时的输出栈栈顶元素
    return STTop(&obj->popst);
}


4️⃣元素出队列(myQueuePop)

错误操作:假设输入栈数据没有全部导入到输出栈里,那么最终的出栈顺序会混乱的。

9934e98638f04326b7cc08a06c1a1d23.gif

正确操作:        

       所以在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。

1bf53bf4fcf644d9b081ef2dba6f897a.gif

代码实现的部分需要注意的:

       可以看出myQueuePop的实现,直接复用了myQueuePeek() ,要不然,对stEmpty判空的逻辑又要重写一遍。

21d071e3d9f94b858639802848bff8b4.png

代码实现:

int myQueuePop(MyQueue* obj) {
    int front = myQueuePeek(obj);//代码复用,获得队列头部元素
    STPop(&obj->popst);//弹出元素
    return front;//获得队列的头部元素
}


5️⃣判空(myQueueEmpty)

如果进栈和出栈都为空的话,说明模拟的队列为空了。

bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->pushst) 
    &&  STEmpty(&obj->popst);
}//判断队列是否为空,需要输入栈和输出栈两个同时为空,才会返回true


6️⃣销毁队列(myQueueFree)

和队列实现栈同样道理,需要先释放两个栈的空间,再释放结构体的空间,这样才确保不会内存泄露

//队列空间的释放
void myQueueFree(MyQueue* obj) {
    STDestroy(&obj->pushst);//释放输入栈
    STDestroy(&obj->popst);//释放输出栈
    free(obj);//释放自定义的队列
}


总代码:

#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include<stdio.h>
typedef int STDataType;
typedef struct Stack
{
  STDataType* a;
  int top;//栈顶的位置
  int capacity;//栈的容量
}ST;
void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst,STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool  STEmpty(ST* pst);
int STSize(ST*pst);
void STInit(ST* pst)
{
  assert(pst);
  pst->a = NULL;//栈底
  //top不是下标
    //pst->top=-1;//指向栈顶元素
  pst->top = 0;//指向栈顶元素的下一个位置
  pst->capacity = 0;
}
void STDestroy(ST* pst)
{
  assert(pst);
  free(pst->a);
  pst->a = NULL;
}
void STPush(ST* pst,STDataType x)
{
  if (pst->top == pst->capacity)
  {
    int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;//true,4.false,括2倍
    STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));//返回值地址相等就是原地扩容,不同就是异地扩
    if (tmp == NULL)
    {
      perror("realloc fail");
      return;
    }
    pst->a = tmp;//返回的是realloc出来的内存块的地址
    pst->capacity = newCapacity;//把扩容后的空间大小赋值给栈容量
  }
  pst->a[pst->top] = x;//先放值
  pst->top++;//再++
}
void STPop(ST* pst)
{
  assert(pst);
  assert(!STEmpty(pst));
  pst->top--;
}
STDataType STTop(ST* pst)
{
  assert(pst);
  assert(!STEmpty(pst));
  return pst->a[pst->top - 1];
}
bool STEmpty(ST* pst)//栈为空返回true,不为空返回false
{
  //assert(pst);
  //if (pst->top == 0)
  //{
  //  return true;
  //}
  //else
  //{
  //  return false;
  //}
  return pst->top == 0;
}
int STSize(ST* pst)
{
  assert(pst);
  return pst->top;
}
typedef struct {
   ST pushst;//输入栈
   ST popst;//输出栈
} MyQueue;//自定义队列
MyQueue* myQueueCreate() {
    //使用malloc函数为MyQueue结构体分配内存空间,定义一个MyQueue*  的指针指向这块空间
    MyQueue* obj =(MyQueue*)malloc(sizeof(MyQueue));
    // 若分配失败
        if(obj == NULL)
    {//则打印错误信息
      perror("malloc fail");
      return NULL;//返回NULL
    }
    STInit(&obj->pushst);//初始化输入栈
    STInit(&obj->popst);//初始化输出栈
    return obj;//返回指向这个结构体的指针
}
void myQueuePush(MyQueue* obj, int x) {
    STPush(&obj->pushst , x);//往队列导入元素 
}
int myQueuePop(MyQueue* obj) {
    int front = myQueuePeek(obj);//代码复用,获得队列头部元素
    STPop(&obj->popst);//弹出元素
    return front;//获得队列的头部元素
}
int myQueuePop(MyQueue* obj) {
  if(STEmpty(&obj->popst))//若输出栈为空
    { 
            //结束条件是把输入栈元素全部挪动到输出栈为止
      while(!STEmpty(&obj->pushst))//输入栈不为空
      {   //获得输入栈头部元素,挪动到输出栈
        STPush(&obj->popst,STTop(&obj->pushst));
        STPop(&obj->pushst);//弹出输入栈元素
      }
    }
        //返回此时的输出栈头部元素
    int front= STTop(&obj->popst);
    STPop(&obj->popst);//弹出元素
    return front;//获得队列的头部元素
}
int myQueuePeek(MyQueue* obj) {
    if(STEmpty(&obj->popst))//若输出栈为空
    { 
            //结束条件是把输入栈元素全部挪动到输出栈为止
      while(!STEmpty(&obj->pushst))//输入栈不为空
      {   //获得输入栈头部元素,挪动到输出栈
        STPush(&obj->popst,STTop(&obj->pushst));
        STPop(&obj->pushst);//弹出输入栈元素
      }
    }
        //返回此时的输出栈头部元素
    return STTop(&obj->popst);
}
bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->pushst) 
    &&  STEmpty(&obj->popst);
}//判断队列是否为空,需要输入栈和输出栈两个同时为空,才会返回true
//队列空间的释放
void myQueueFree(MyQueue* obj) {
    //和队列实现栈同样道理,需要先释放两个栈的空间,再释放结构体的空间,这样才确保不会内存泄露
    STDestroy(&obj->pushst);//释放输入栈
    STDestroy(&obj->popst);//释放输出栈
    free(obj);//释放自定义的队列
}


代码执行:

f7431b8d260f4047bbe74e917835531a.png

   本文结束,如有错误,欢迎指正,感谢你的来访!🚩

相关文章
|
19天前
|
SQL 分布式计算 监控
Sqoop数据迁移工具使用与优化技巧:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入解析Sqoop的使用、优化及面试策略。内容涵盖Sqoop基础,包括安装配置、命令行操作、与Hadoop生态集成和连接器配置。讨论数据迁移优化技巧,如数据切分、压缩编码、转换过滤及性能监控。此外,还涉及面试中对Sqoop与其他ETL工具的对比、实际项目挑战及未来发展趋势的讨论。通过代码示例展示了从MySQL到HDFS的数据迁移。本文旨在帮助读者在面试中展现Sqoop技术实力。
61 2
|
19天前
|
监控 负载均衡 Cloud Native
ZooKeeper分布式协调服务详解:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入剖析ZooKeeper分布式协调服务原理,涵盖核心概念如Server、Client、ZNode、ACL、Watcher,以及ZAB协议在一致性、会话管理、Leader选举中的作用。讨论ZooKeeper数据模型、操作、会话管理、集群部署与管理、性能调优和监控。同时,文章探讨了ZooKeeper在分布式锁、队列、服务注册与发现等场景的应用,并在面试方面分析了与其它服务的区别、实战挑战及解决方案。附带Java客户端实现分布式锁的代码示例,助力提升面试表现。
32 2
|
19天前
|
数据采集 消息中间件 监控
Flume数据采集系统设计与配置实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入探讨Apache Flume的数据采集系统设计,涵盖Flume Agent、Source、Channel、Sink的核心概念及其配置实战。通过实例展示了文件日志收集、网络数据接收、命令行实时数据捕获等场景。此外,还讨论了Flume与同类工具的对比、实际项目挑战及解决方案,以及未来发展趋势。提供配置示例帮助理解Flume在数据集成、日志收集中的应用,为面试准备提供扎实的理论与实践支持。
28 1
|
2月前
|
存储 缓存 算法
Python中collections模块的deque双端队列:深入解析与应用
在Python的`collections`模块中,`deque`(双端队列)是一个线程安全、快速添加和删除元素的双端队列数据类型。它支持从队列的两端添加和弹出元素,提供了比列表更高的效率,特别是在处理大型数据集时。本文将详细解析`deque`的原理、使用方法以及它在各种场景中的应用。
|
7天前
|
存储 算法 Java
耗时3天写完的HashMap万字解析,争取一篇文章讲透它,面试官看了都直点头!
耗时3天写完的HashMap万字解析,争取一篇文章讲透它,面试官看了都直点头!
41 3
|
7天前
|
存储 Java C++
Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列
Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列
18 0
|
10天前
|
数据采集 机器学习/深度学习 数据挖掘
Python数据清洗与预处理面试题解析
【4月更文挑战第17天】本文介绍了Python数据清洗与预处理在面试中的常见问题,包括Pandas基础操作、异常值处理和特征工程。通过示例代码展示了数据读取、筛选、合并、分组统计、离群点检测、缺失值和重复值处理、特征缩放、编码、转换和降维。强调了易错点,如忽视数据质量检查、盲目处理数据、数据隐私保护、过度简化特征关系和忽视模型输入要求。掌握这些技能和策略将有助于在面试中脱颖而出。
25 8
|
13天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
30 0
|
13天前
|
API Python
Python模块化编程:面试题深度解析
【4月更文挑战第14天】了解Python模块化编程对于构建大型项目至关重要,它涉及代码组织、复用和维护。本文深入探讨了模块、包、导入机制、命名空间和作用域等基础概念,并列举了面试中常见的模块导入混乱、不适当星号导入等问题,强调了避免循环依赖、合理使用`__init__.py`以及理解模块作用域的重要性。掌握这些知识将有助于在面试中自信应对模块化编程的相关挑战。
21 0
|
18天前
|
机器学习/深度学习 分布式计算 BI
Flink实时流处理框架原理与应用:面试经验与必备知识点解析
【4月更文挑战第9天】本文详尽探讨了Flink实时流处理框架的原理,包括运行时架构、数据流模型、状态管理和容错机制、资源调度与优化以及与外部系统的集成。此外,还介绍了Flink在实时数据管道、分析、数仓与BI、机器学习等领域的应用实践。同时,文章提供了面试经验与常见问题解析,如Flink与其他系统的对比、实际项目挑战及解决方案,并展望了Flink的未来发展趋势。附带Java DataStream API代码样例,为学习和面试准备提供了实用素材。
72 0

推荐镜像

更多