class 014 队列和栈相互成全

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解这个题目了, 所以就将整个过程写下来了。

这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐.
https://space.bilibili.com/8888480?spm_id_from=333.999.0.0

顺便提供一下测试链接:

  1. 用栈实现队列:https://leetcode.cn/problems/implement-queue-using-stacks/
  2. 用队列实现栈:https://leetcode.cn/problems/implement-stack-using-queues/

1. 用栈实现队列

在这里插入图片描述

1.1 逻辑结构

可以用两个“栈”实现一个队列, 一个栈是 in栈, 另一个是 out栈, 用一段数据放入 in栈, 然后将 in栈 中的数据放入 out栈 因为“栈”的特点是:后进先出, 所以经过两次后进先出就成了先进先出了.

下面的图片可以很好解释, 将 a, b, c, d 放入 in栈, 再将 in栈 中的数据进行弹出放到 out栈, 最后将 out栈 中的数据弹出, (所有实现都是利用 的后进先出的方式), 这样最后的数据也是:“先进先出”了.

在这里插入图片描述

1.2 代码实现

所有的实现都在下面的代码中, 而且每一条代码写上了注释, 应该可以理解.

以下的所有方法, 若是有理解不了的地方, 可以自己画一下逻辑结构, 可以尝试放入一组数据, 简单画一下就能理解.

public class ConvertQueueAndStack {     
    class MyQueue {  
  
       public Stack<Integer> in;      // 使用系统自己的“栈”结构  
  
       public Stack<Integer> out;  
  
       public MyQueue() {  
          in = new Stack<Integer>();  
          out = new Stack<Integer>();  
       }  
  
       // 看下面的代码的时候, 要结合“队列”的特点来看.  
  
       // 倒数据  
       // 从in栈,把数据倒入out栈  
       // 1) out空了,才能倒数据  
       // 2) 如果倒数据,in必须倒完  
       private void inToOut() {  
          if (out.empty()) { // 保证“out栈”为“空”, 才能将“in栈”中的数据倒进去.  
             while (!in.empty()) {   // 先判断“in栈”中是不是“空”, 只要不是“空”就全部倒进“out”栈  
                out.push(in.pop());  
                // 这行代码的意思是:将“in栈”中的数据弹出来一个, 然后再由“out栈”将弹出来的数据接收.  
             }  
          }  
       }  
  
       public void push(int x) {  
          in.push(x);             // 先往“in栈”中放进去一个“数据”,  
          inToOut();      // 然后立刻将“in栈”中的数据放入“out栈”. 要是能放进去就放, 不能放就还存在“in栈”中.  
       }                           // 因为我们拿出“数据”都是从“out栈”中拿出.  
  
       public int pop() {  
          inToOut();              // 相当于先检查一下“out栈”中有没有数据, 要是没有数据根本就不会弹出数据.  
          return out.pop();       // 然后弹出“out栈”中最上方的“数据”.  
       }  
  
  
       // 结合“队列”的特点, 队列的顶部(头部)是“l”, 那这样的话, 不就是“out”栈中顶部的数据吗.  
       public int peek() {      // 和上面一样, 先检查一下“out栈”中有没有数据, 要是没有数据根本就返回不了.  
          inToOut();  
          return out.peek();  // 然后返回“out栈”中的“栈顶元素”.  
       }  
  
       public boolean empty() {  
          return in.isEmpty() && out.isEmpty();// 判断这个“队列”中为不为空, 只有“in栈和out栈”同时不为“空”才行.  
       }                                        // 因为只要为任何一个为“空”就相当于整个“队列”为“空”.  
  
    }

1.3 时间复杂度分析

结论:均摊之后的时间复杂度是:O(1).

分析:因为一个“数据”, 最开始是放入 in栈, 然后从 in栈 出, 放入 out栈, 然后从 out栈 出来, 让用户使用, 无论怎么样, 进去多少个数字, 平均每一个数字最多执行 4 次, 因为不可能从 out栈 回到 in栈, 哪怕是倒入 1000000000 个数字, 那每一个数字均摊下来的时间复杂度是:O(1).

2. 用队列实现栈

在这里插入图片描述

2.1 逻辑结构

注意:这里的队列就是货真价实的队列, 没有什么别的特殊的性质, 就是一个最基本的队列, 一定是符合 先进先出 的特点的.

  1. 将“数据”放入队列中, 只能从尾部放入, 也只能从头部出.
  2. 先放入一个“数据 a”, 此时不用管任何事, 队列中只有 a
  3. 然后放入下一个数据 b (继续放入数据), 放入之后, 此时队列元素是:a, b, 将 a 从头部放出, 然后从尾部放入, 此时队列元素是:b, a.
  4. 之后以此类推, 先记录一下放入“数据”之前, 队列中原来有几个元素, 然后将数据放入, 将原来所有的元素按顺序从队列的“头部出, 尾部进”, 这样就实现了用“队列实现栈”, 并且此时的“栈”的“栈底”是“队列的尾部”.

过程如下图所示, 自己可以随便用组数据测试一下就能理解了.

在这里插入图片描述

2.2 代码实例

所有的实现都在下面的代码中, 而且每一条代码写上了注释, 应该可以理解.

以下的所有方法, 若是有理解不了的地方, 可以自己画一下逻辑结构, 可以尝试放入一组数据, 简单画一下就能理解.

class MyStack {  
  
    Queue<Integer> queue;  
  
    public MyStack() {  
       queue = new LinkedList<Integer>();  
    }  
  
    // O(n)  
    public void push(int x) {  
       int n = queue.size();      // 先确定放入数据之前, 队列中有几个数字.  
       queue.offer(x);            // 然后将“数据”进行放入  
       for (int i = 0; i < n; i++) {  
          queue.offer(queue.poll());  // 最后将“放入数据”之前的所有的数据都从头部弹出, 从尾部放入.  
       }                               // 这样就能实现一个“栈”结构了.  
    }  
  
    public int pop() {         // 因为想要弹出, 肯定需要放入数据, 所以原来已经执行过“push”操作了,  
       return queue.poll();     // 所以这里直接进行弹出就可以, 肯定是以“栈”的方式进行弹出的.  
    }  
  
  
    public int top() {  
       return queue.peek();    // 也是直接返回头部就可以. 还是和原来一样, 肯定执行了“push”  
    }                           // 所以直接返回“队列”的头部就行了,  
  
    public boolean empty() {  
       return queue.isEmpty();       // 直接判断是不是“空”, 这样不用说.  
    }  
  
}

2.3 时间复杂度分析

结论:时间复杂度是:O(n).

分析:这个方法中, 每次进来一个数字, 都是要计算一下“队列”中原来的数字有多少, 所以这样就表示我放进来的数字越多, 那我需要执行的次数就越多, 比如我放入一个数字之前, “队列”中原来的数字有 10 个, 那我就要将这 10 个数字拿出来再放进去, 若是有 100 个, 我就要将这 100 个数字拿出来再放进去, 所以这个的时间复杂度是 O(n).

也可以这样理解:我放进来的数字越多, 那就说明我需要移动的次数越多 (比如拿 第一个放进来元素) 为例, 若是一直放进来, 那原来放进来的元素就一直会进行移动, 这样移动次数是不确定的. 所以均摊下来, 只要我放进去 1 个数字, 那就将所有的数字执行 1 次, 每一个“数据”的时间复杂度是 O(n).

或者直接算一遍:放入 1 个“数据”前面需要执行 0 次, 放入 2 个数据, 前面需要执行 1 * 2 次, 放入 3 个“数据”前面需要执行 2 * 2 次, 所以以此类推:放入 n 个数字, 需要执行 (n - 1) * 2 次, 所以最后的时间复杂度是 O(2n - 1) -> O(n). (* 2 的是因为要从头弹出, 再从尾进去).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值