用栈实现队列 & 用队列实现栈

这里写目录标题

  • 前言
    • 源码
  • 栈实现队列
    • 思路
    • 容器设计
    • 类定义
    • 方法实现
      • 1. 栈的“倒腾”
      • 2. 入队操作
      • 3.出队操作
  • 队列实现栈
    • 思路
    • 容器设计
    • 类定义
    • 方法实现
      • 1. 最简单的empty方法
      • 2. 入栈方法
      • 3. 队列转换方法
      • 4. 出栈方法
      • 5. 取栈顶元素方法
  • 总结

前言

题目上的这两题可以说是经典题目了:
剑指 Offer 09: 双栈实现队列
leetcode 225: 队列实现栈

有关于栈和队列,我们之前也讨论过:
从零开始手撕一个数据结构(2)——基于链表实现栈和队列

我们都知道,栈和队列,一个是LIFO,一个是FIFO,那么怎么用栈去实现队列,用队列去实现栈呢?

源码

  • 双队列实现栈
  • 双栈实现队列

栈实现队列

我们知道,每次从栈中取一个元素都是从栈顶取的,如果我们要用栈实现一个队列,那这个【栈实现的队列】中每次【出队】的元素都是在栈底中取的,怎么样才能取到栈底元素呢?

剑指Offer 09 的题目要求是:用两个栈实现队列

思路

用两个栈的话就很好解决【如何取栈底元素】这个问题了:

  • 将栈1的元素全部取到栈2,则栈2中存放元素的顺序和原来相反,也就是栈2的栈顶==栈1的栈底
  • 我们使用栈1存储元素,在实例触发【出队】操作时,若栈2为空,将栈1中的全部元素取到栈2,再返回栈2的栈顶,若栈2不为空,则直接返回栈2的栈顶
  • 在栈1,栈2都为空时,即代表【栈实现的队列】为空,依题意返回 -1

容器设计

题目要求是:

实现appendTail 和 deleteHead 两个方法, 分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

其中,appendTail相当于队列中的offer,即入队,而deleteHead则相当于队列中的poll,即出队,按照我们刚才的思路来,很容易就可以解决这个问题

类定义

public class StackImplQueue {
     
    /**
     * 这里用了java.util.Stack的栈,在速度上会比较慢,用LinkedList会快些,但没必要^_^
     */
    Stack<Integer> s1;
    Stack<Integer> s2;

    public StackImplQueue() {
     
        s1 = new Stack<>();
        s2 = new Stack<>();
    }
}

这里用了Stack,不用其它容器是因为其它容器的实现乱七八糟的

方法实现

1. 栈的“倒腾”

刚才我们的思路中,有将栈1元素全部取出放到栈2的操作,我称其为【栈的“倒腾”】,这就像你把一箱玩具倒到另一箱,原本在压箱底的玩具就会在另一箱的箱顶,具体操作如下

    /**
     * 将栈1倒腾到栈2上的方法
     */
    public void stackTransfer() {
     
        while (!s1.isEmpty()) {
     
            s2.push(s1.pop());
        }
    }

这里栈2一直接受栈1的出栈元素,直到栈1为空,应该很好理解吧?

2. 入队操作

    /**
     * 添加操作时,往栈1添加元素
     *
     * @param value
     */
    public void appendTail(int value) {
     
        s1.push(value);
    }

前面说了,栈1就是用来存东西的,所以入队时直接在栈1入栈就行了

3.出队操作

    /**
     * 删除操作时,当两个栈都为空,返回-1
     * 当栈2为空,把栈1的元素倒腾到栈2上,再用栈2出栈(栈反转就是队列)
     *
     * @return
     */
    public int deleteHead() {
     
        if (s1.isEmpty() && s2.isEmpty()) {
     
            return -1;
        }
        if (s2.isEmpty()) {
     
            stackTransfer();
        }
        return s2.pop();
    }

和刚才的操作一样,只有当栈2取空的时候才需要倒腾栈1,不然就乱序了

队列实现栈

那么,用队列如何实现栈呢?先看看【leetcode 225: 队列实现栈】的题目要求:

你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

题目只对队列的操作做了要求,对实现方法并无特殊要求,既然刚才都用两个栈实现队列了,这次也用两个队列实现栈吧

思路

  • 队列是FIFO的数据结构,要像栈一样出栈,取的元素就是队列的队尾
  • 容器初始化后,我们将数据装入队列1中,触发出栈时,将队列1中的数据逐个出队装入队列2中,直到队列1剩下最后一个数据时,返回这个仅剩的数据
  • 此时数据在队列2中
  • 为保证数据的换队有序——数据在队列1中时要把数据换到队列2,反之亦然,容器维护一个布尔类型的状态值boolean status,当status==false时,往队列1装数据,反之则往队列2装数据
  • 每次触发出栈时都会对status进行取反操作,以表示当前是哪个队列在存储数据

容器设计

题目要求:

使用队列实现栈的下列操作:
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空

只需要设计这四个方法就行了

类定义

public class QueueImplStack {
     

    Queue<Integer> q1;
    Queue<Integer> q2;
    int size;

    /**
     * 容器状态,当status==false时,q1为元素放置队列,否则q2为元素放置队列
     */
    boolean status;

    /** Initialize your data structure here. */
    public QueueImplStack() {
     
        this.q1 = new LinkedList<>();
        this.q2 = new LinkedList<>();
    }
}
  • 两条队列q1,q2
  • size字段记录容器存储的元素个数
  • status字段即刚才说的容器状态
  • 构造方法不对size和status字段赋值,默认为零值,即0、false

方法实现

1. 最简单的empty方法

    /** Returns whether the stack is empty. */
    public boolean empty() {
     
        return size==0;
    }

2. 入栈方法

    /** Push element x onto stack. */
    public void push(int x) {
     
        // 向元素放置队列添加新元素
        if(status){
     
            q2.offer(x);
        }else{
     
            q1.offer(x);
        }
        ++size;
    }

当status为false时,向队列1入队,反之向队列2入队

3. 队列转换方法

由于是两个队列实现的栈,数据的移动也是必须的

    /**
     * 队列转换,将当前放置元素所用的队列poll到另一条空队列,直到当前队列仅剩一个元素
     * pop/top操作取栈顶元素时使用
     */
    private void queueTransfer(){
     
        for(int tmpSize=size-1;tmpSize>0;--tmpSize){
     
            if(status){
     
                q1.offer(q2.poll());
            }else{
     
                q2.offer(q1.poll());
            }
        }
    }

获取size的个数再减1,然后循环出队入队

4. 出栈方法

    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
     
        queueTransfer();
        // 根据状态,取当前放置元素队列仅剩的一个元素并出队
        int res = status?q2.poll():q1.poll();
        // 更改状态,使当前放置元素队列更改为另一队列
        status=!status;
        --size;
        return res;
    }
  • 每次出栈时触发队列转换
  • 再取当前放置元素队列中仅剩的元素出队
  • 更改容器状态以标识新的放置元素队列
  • 维护size

5. 取栈顶元素方法

    /** Get the top element. */
    public int top() {
     
        queueTransfer();
        // 根据状态,取当前放置元素队列仅剩的一个元素
        int res = status?q2.peek():q1.peek();
        // 将当前放置元素队列的最后一个元素移动到另一队列
        if(status){
     
            q1.offer(q2.poll());
        }else{
     
            q2.offer(q1.poll());
        }
        // 更改状态,使当前放置元素队列更改为另一队列
        status=!status;
        return res;
    }

与出栈方法基本一致,只不过这里不对队尾元素进行出队,而是用peek取值之后再将其放到另一队列

总结

由于在 leetcode 都通过了,就不测试了
这两题是比较经典的题目,我的方法也不是最好的,有更好方法的同学欢迎留言交流^_^

你可能感兴趣的:(从零开始手撕一个数据结构,数据结构,队列,栈,java,queue)