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

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

😻前言


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


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


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


如何用栈实现队列?


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


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


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


fe5a9bd6da474f808573b5777ccc67dc.gif

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


用栈实现队列


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

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

d0bdd64d8b694dbf8fd8e7896872f3e6.png38901c245cb54a5e8c683dd796176142.png

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

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;
}


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

相关代码实现:

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


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

相关代码实现:

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


  • 接着就是对入队列操作的实现。
  • 前面说过,无论如何,只需要在入数据的栈中入栈即可。

相关代码实现:

void 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);
}


整体的实现代码


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);
}


😼写在最后


💝学会了用队列实现栈,也学会了用栈实现队列,回想一下,还是挺简单的,下一篇将带大家设计一个循环队列,难度会有些上升噢,大家打起精神来!!!

❤️‍🔥后续将会持续输出有关数据结构与算法的文章,你们的支持就是我写作的最大动力!


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

相关文章
|
17天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
91 9
|
8天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
17 1
|
11天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
14天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
16天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
43 4
|
20天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
30 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
1月前
初步认识栈和队列
初步认识栈和队列
60 10
|
1月前
数据结构(栈与列队)
数据结构(栈与列队)
18 1
|
1月前
|
算法
数据结构与算法二:栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式
这篇文章讲解了栈的基本概念及其应用,并详细介绍了中缀表达式转换为后缀表达式的算法和实现步骤。
46 3