双栈队列:一种独特的队列实现方式
前言
当我们想象队列时,可以想象成排队等待进入某个地方的人群,比如购票、办理业务或是排队上车。在计算机科学中,队列也是一种常见的数据结构,它遵循着和现实生活中队列一样的规则:先进先出(First-In-First-Out,FIFO)。
这意味着最先进入队列的元素将会最先被处理或者移除,就像排在队伍前面的人会先被服务一样。队列在计算机科学中被广泛应用,例如任务调度、缓存管理、广度优先搜索等领域。深入理解队列的原理和应用,有助于我们更好地解决实际问题,提高程序的效率和性能。
正文
队列的介绍
在JavaScript中,队列和栈一样,没有单独的结构,都是通过使用数组模拟的。你可以把队列看作”初步阉割版“数组,把栈看作”完全阉割版“数组。
在使用数组模拟队列时,通常使用push
和shift
两种方法实现入队和出队,也可以使用unshift
和pop
方法实现。
在队列中,元素只能从队尾进入,从队头出去,而数组的元素可以在数组的任何位置进出。
-
创建一个队列:
let queue = []
-
入对操作:
queue.push(element)
-
出队操作:
queue.shift()
-
获取出队元素:
let element = queue.shift()//shift方法会返回出队元素的值
-
获取队列长度:
let length =queue.length()
-
获取队首元素:
如果只需要获取队列的第一个元素而不移除它,可以使用数组的下标获取队列的队首元素。
let firstElement = queue[0];
注意:在队列中,只能获取队首元素,不能获取其他元素。因为队列里的元素就像在排成一列站军姿,教官只能看见队列第一个人,如果要看第二个人就需要让第一个人出队列。
双端队列
双端队列是一种特殊的队列。它结合了队列和栈的特性,允许在队列的两端进行插入和删除操作。双端队列的名称中的 "双端" 表示它可以同时在队列的前端(头部)和后端(尾部)进行操作。
双端队列与普通队列的主要区别在于,普通队列只允许在队列的尾部插入元素并从头部移除元素,而双端队列允许在队列的两端同时进行插入和删除操作。
算法题(力扣232)
题目
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 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
- 最多调用
100
次push
、pop
、peek
和empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用
pop
或者peek
操作)
思考
题目让我们通过两个栈(stack1和stack2)实现队列。那我们分别分析栈和队列的特点。队列是先进先出,后进后出,栈是先进后出,后进先出。
大体思路:
在队列中,元素的出队顺序就是入队顺序。当入队元素以入队顺序依次压入stack1中,如果元素依次通过stack1出栈,会得到相反的出队顺序。就相当于用一个栈会颠倒顺序,那使用两个栈就可以将出队顺序颠倒回来。
那我们可以将元素压入stack1中,然后再将stack1的元素依次出栈并压入stack2中。经过这样倒转一下,stack2的出栈顺序就和入队顺序(出队顺序)一样了。
我们可以把stack1当作队列的队尾,把stack2当作队列的对头。
图解:元素1,2,3,4按从左到右的顺序依次入队,由两个栈实现。
-
入队元素依次压入stack1中。
-
将stack1的所有元素依次压入stack2中。
-
将stack2中的所有元素依次出栈。
最终的出栈顺序为1,2,3,4。
图解:元素1,2,3,4按从左到右的顺序依次入队,由队列实现。
-
依次入队。
-
依次出队。
最终出队顺序为1,2,3,4。
具体实现
-
实现
void push(int x)
将元素 x 推到队列的末尾。入队的元素,我们只要压入stack1就好了。
/** * @param {number} x * @return {void} */ MyQueue.prototype.push = function (x) { this.stack1.push(x) };
-
实现
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() };
-
实现
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] };
-
实现
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