likes
comments
collection
share

双栈队列:一种独特的队列实现方式

作者站长头像
站长
· 阅读数 28

前言

当我们想象队列时,可以想象成排队等待进入某个地方的人群,比如购票、办理业务或是排队上车。在计算机科学中,队列也是一种常见的数据结构,它遵循着和现实生活中队列一样的规则:先进先出(First-In-First-Out,FIFO)。

这意味着最先进入队列的元素将会最先被处理或者移除,就像排在队伍前面的人会先被服务一样。队列在计算机科学中被广泛应用,例如任务调度、缓存管理、广度优先搜索等领域。深入理解队列的原理和应用,有助于我们更好地解决实际问题,提高程序的效率和性能。

正文

队列的介绍

在JavaScript中,队列和栈一样,没有单独的结构,都是通过使用数组模拟的。你可以把队列看作”初步阉割版“数组,把栈看作”完全阉割版“数组。

在使用数组模拟队列时,通常使用pushshift两种方法实现入队和出队,也可以使用unshiftpop方法实现。

双栈队列:一种独特的队列实现方式

在队列中,元素只能从队尾进入,从队头出去,而数组的元素可以在数组的任何位置进出。

  • 创建一个队列:

    let queue = []
    
  • 入对操作:

    queue.push(element)
    
  • 出队操作:

    queue.shift()
    
  • 获取出队元素:

    let element = queue.shift()//shift方法会返回出队元素的值
    
  • 获取队列长度:

    let length =queue.length()
    
  • 获取队首元素:

    如果只需要获取队列的第一个元素而不移除它,可以使用数组的下标获取队列的队首元素。

    let firstElement = queue[0];
    

    注意:在队列中,只能获取队首元素,不能获取其他元素。因为队列里的元素就像在排成一列站军姿,教官只能看见队列第一个人,如果要看第二个人就需要让第一个人出队列。

双端队列

双端队列是一种特殊的队列。它结合了队列和栈的特性,允许在队列的两端进行插入和删除操作。双端队列的名称中的 "双端" 表示它可以同时在队列的前端(头部)和后端(尾部)进行操作。

双端队列与普通队列的主要区别在于,普通队列只允许在队列的尾部插入元素并从头部移除元素,而双端队列允许在队列的两端同时进行插入和删除操作。

双栈队列:一种独特的队列实现方式

算法题(力扣232)

题目

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

示例

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

提示

  • 1 <= x <= 9
  • 最多调用 100pushpoppeekempty
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)

思考

题目让我们通过两个栈(stack1和stack2)实现队列。那我们分别分析栈和队列的特点。队列是先进先出,后进后出,栈是先进后出,后进先出。

大体思路:

在队列中,元素的出队顺序就是入队顺序。当入队元素以入队顺序依次压入stack1中,如果元素依次通过stack1出栈,会得到相反的出队顺序。就相当于用一个栈会颠倒顺序,那使用两个栈就可以将出队顺序颠倒回来。

那我们可以将元素压入stack1中,然后再将stack1的元素依次出栈并压入stack2中。经过这样倒转一下,stack2的出栈顺序就和入队顺序(出队顺序)一样了。

我们可以把stack1当作队列的队尾,把stack2当作队列的对头。

图解:元素1,2,3,4按从左到右的顺序依次入队,由两个栈实现。

  1. 入队元素依次压入stack1中。

双栈队列:一种独特的队列实现方式

  1. 将stack1的所有元素依次压入stack2中。

双栈队列:一种独特的队列实现方式

  1. 将stack2中的所有元素依次出栈。

双栈队列:一种独特的队列实现方式

最终的出栈顺序为1,2,3,4。

图解:元素1,2,3,4按从左到右的顺序依次入队,由队列实现。

  1. 依次入队。

双栈队列:一种独特的队列实现方式

  1. 依次出队。

双栈队列:一种独特的队列实现方式

最终出队顺序为1,2,3,4。

具体实现

  1. 实现void push(int x)将元素 x 推到队列的末尾。

    入队的元素,我们只要压入stack1就好了。

    /** 
     * @param {number} x
     * @return {void}
     */
    MyQueue.prototype.push = function (x) {
        this.stack1.push(x)
    };
    
  2. 实现int pop() 从队列的开头移除并返回元素。

    我们只需要将stack2的栈顶元素出栈就可以实现元素出队。

    但是除了stack1和stack2都为空的情况下,我们需要保持stack2里有元素可以出栈,所以我们要判断stack2的长度:

    • 如果长度为0,则需要将stack1里的元素全部依次压入stack2中,然后让一个元素出栈,返回出栈元素值。
    • 如果长度不为0,让一个元素出栈,返回出栈元素值。
    /**
     * @return {number}
     */
    MyQueue.prototype.pop = function () {
        if (this.stack2.length === 0) {
            while (this.stack1.length) {
                this.stack2.push(this.stack1.pop())
            }
        }
        return this.stack2.pop()
    };
    
  3. 实现int peek() 返回队列开头的元素。

    我们只需要通过数组下标访问stack2的栈顶元素就可以实现。

    但是,我们也要判断stack2里是否有元素。所以我们要判断stack2的长度:

    • 如果长度为0,则需要将stack1里的元素全部依次压入stack2中,然后返回栈顶元素的值。
    • 如果长度不为0,则返回栈顶元素的值。
    /**
     * @return {number}
     */
    MyQueue.prototype.peek = function () {
        if (this.stack2.length === 0) {
            while (this.stack1.length) {
                this.stack2.push(this.stack1.pop())
            }
        }
        return this.stack2[this.stack2.length - 1]
    };
    
  4. 实现boolean empty() 如果队列为空,返回 true ;否则,返回 false

    我们只需要判断stack1和stack2是否为空。

    /**
     * @return {boolean}
     */
    MyQueue.prototype.empty = function () {
        return this.stack1.length === 0 && this.stack2.length === 0
    };
    

最终结果:

双栈队列:一种独特的队列实现方式

完整代码

var MyQueue = function () {
    this.stack1 = []
    this.stack2 = []
};

/** 
 * @param {number} x
 * @return {void}
 */
MyQueue.prototype.push = function (x) {
    this.stack1.push(x)
};

/**
 * @return {number}
 */
MyQueue.prototype.pop = function () {
    if (this.stack2.length === 0) {
        while (this.stack1.length) {
            this.stack2.push(this.stack1.pop())
        }
    }
    return this.stack2.pop()
};

/**
 * @return {number}
 */
MyQueue.prototype.peek = function () {
    if (this.stack2.length === 0) {
        while (this.stack1.length) {
            this.stack2.push(this.stack1.pop())
        }
    }
    return this.stack2[this.stack2.length - 1]
};

/**
 * @return {boolean}
 */
MyQueue.prototype.empty = function () {
    return this.stack1.length === 0 && this.stack2.length === 0
};

小结

深入了解一个数据结构的特点,可以让我们在算法题中有更巧妙的解法。

转载自:https://juejin.cn/post/7367164214080995378
评论
请登录