JavaScript資料結構之隊棧互搏
今天稍微停下前進的腳步,來看下隊棧的左右互搏術。 前兩天學習了佇列和棧以後,今天就可以試著來用兩個棧實現佇列的功能 或者 用兩個佇列來實現棧的功能。
資料結構之---棧實現佇列
1. 用兩個棧實現一個佇列
1.1 題目分析
棧是先進後出,佇列是先進先出,但可以用兩個棧來模擬一個佇列的功能,來實現佇列中主要的enqueue,dequeue, head 方法。
1.2 思路分析
我們所學的每一種資料結構,本質上都是對資料如何儲存和使用的研究,這就必然涉及到增刪改查,那麼考慮實現這些方法時,我們優先考慮如何實現資料的增加,只有存在了資料,才能夠後續的操作。 所以如何實現佇列中的增加資料方法enqueue呢?
- 給兩個棧分別命名為 stack1,stack2。那有了這兩個棧以後,可以選取其中一個來儲存資料,比如說stack1,那麼佇列的enqueue方法就很容易了,直接利用棧的push方法就能夠新增資料。
接下來考慮佇列中的刪除dequeue方法
- 首先要注意下dequeue方法是刪除佇列中的頭部元素,而此時隊首是在stack1棧底的,目前來說還取不到。
- 這個時候stack2該上場了,可以把stack1中的元素都依次移除並壓入stack2中,這樣的話,stack2的棧頂就變成了隊首,不就可以利用stack2的pop方法來移除元素了嘛。
那佇列的head方法呢
- 執行完stack2的pop方法後,還需要把資料再移回stack1裡嗎? 其實不需要了,因為此時隊首正好是stack2的棧頂,而佇列的head方法就可以利用棧的top方法來實現了。
- 如果stack2是空的怎麼辦?那stack1的元素都移除到stack2就可以了。
- 如果stack1也是空的呢,那就說明佇列中沒有元素了,此時返回null就可以了。
注意到了嗎,這裡又用到了***分而治之*** 的思想,還記得之前在哪裡用過嗎? 對,就是在給棧新增獲取最小值方法的時候用過,當時也是用了兩個棧來實現。 這裡的話enqueue始終都操作stack1,dequeue和head方法始終都操作stack2。
1.3 程式碼實現
{
class StackQueue {
constructor() {
this.stack1 = new Stack();
this.stack2 = new Stack();
}
// 初始化stack,偽造私有方法
_initStack() {
if (this.stack1.isEmpty() && this.stack2.isEmpty()) {
return null; // 如果兩個棧都是空的,那麼佇列中就沒有元素
}
if (this.stack2.isEmpty()) {
// 如果stack2是空的,那麼此時stack1一定不為空
while (!this.stack1.isEmpty()) {
this.stack2.push(this.stack1.pop()); // 把stack1的元素移除到stack2中
}
}
}
// 向隊尾新增一個元素
enqueue(item) {
this.stack1.push(item); // 把資料存入到stack1中
}
// 刪除隊首的一個元素
dequeue() {
this._initStack();
return this.stack2.pop();
}
// 返回隊首的元素
head() {
this._initStack();
return this.stack2.top();
}
}
var stackQueue = new StackQueue();
stackQueue.enqueue(1);
stackQueue.enqueue(4);
stackQueue.enqueue(8);
console.log(stackQueue.head()); // 1
stackQueue.dequeue();
stackQueue.enqueue(9);
console.log(stackQueue.head()); // 4
stackQueue.dequeue();
console.log(stackQueue.head()); // 8
console.log(stackQueue.dequeue()); // 8
console.log(stackQueue.dequeue()); // 9
}
複製程式碼
是不是覺得很簡單呢,梳理清楚佇列和棧的特性就OK啦。 接下來讓我們繼續修煉,用佇列實現棧吧!
2. 用兩個佇列實現一個棧
2.1 題目分析
佇列是先進先出,棧是先進後出,(不斷重複這兩個知識點) 但可以用兩個佇列來模擬一個棧的功能,來實現棧中主要的push,pop, top 方法。
你可能會想到利用上邊的套路來實現這個需求,但是最後的結果你會發現是不正確的。因為 把stack1的元素移除到stack2中,此時的兩個棧中的資料就首尾交換了,而如果此處換成佇列 this.queue2. enqueue(this.queue1. dequeue()), 你會發現由於佇列的特性,此時的兩個佇列還是一樣的,首尾並沒有交換。
so 我們來換個思路
2.2 思路分析
和上邊一樣,我們先考慮如何實現棧的儲存資料push方法:
- 給兩個佇列分別命名為 queue1,queue2。實現push方法時,利用佇列的enqueue方法,如果兩個佇列都為空,那麼預設向queue1裡新增資料;如果有一個不為空,那麼就向這個不為空的佇列裡新增資料。
top方法就簡單了:
- 利用佇列的tail方法,兩個佇列要麼都為空,要麼有一個不為空,那麼返回不為空佇列的尾部元素就是棧頂元素了.
接下來思考比較複雜的pop方法:
- pop方法刪除的是棧頂,但此時棧頂元素是佇列的尾部元素,而隊尾元素是不能刪除的。
- 但每次執行pop時,可以將不為空的a佇列裡的元素迴圈刪除並放入到另一個b佇列中,直到a佇列中只剩下一個元素,此時a佇列的這個元素就是佇列的尾部元素,也就是棧頂元素了,那pop方法就簡單了,利用a佇列的dequeue方法就可以了。
在具體的實現中,需要額外定義兩個變數,dataQueue和emptyQueue:
- dataQueue始終指向那個不為空的佇列
- emptyQueue始終指向那個為空的佇列
2.3 程式碼實現
{
class QueueStack {
constructor() {
this.queue1 = new Queue();
this.queue2 = new Queue();
this.dataQueue = null; // 存放資料的佇列
this.emptyQueue = null; // 存放備份資料的佇列
}
// 初始化佇列資料,模擬私有方法 確認哪個佇列存放資料,哪個佇列做備份
_initQueue() {
if (this.queue1.isEmpty()) {
this.dataQueue = this.queue2;
this.emptyQueue = this.queue1;
} else {
// 都為空的話 預設是 佇列1
this.dataQueue = this.queue1;
this.emptyQueue = this.queue2;
}
}
// 往棧裡壓入一個元素
push(item) {
this._initQueue();
this.dataQueue.enqueue(item);
}
// 返回棧頂的元素
top() {
this._initQueue();
return this.dataQueue.tail();
}
// 把棧頂的元素移除
pop() {
this._initQueue();
while (this.dataQueue.size() > 1) {
// 利用備份佇列轉移資料,
this.emptyQueue.enqueue(this.dataQueue.dequeue()); // 資料佇列和備份佇列交換了身份
}
return this.dataQueue.dequeue(); // 移除資料佇列的頭部元素
}
}
var queueStack = new QueueStack();
queueStack.push(1);
queueStack.push(2);
queueStack.push(4);
console.log(queueStack.top()); // 棧頂是 4
console.log(queueStack.pop()); // 移除 4
queueStack.push(5);
console.log(queueStack.top()); // 棧頂變成 5
queueStack.push(6);
console.log(queueStack.pop()); // 移除 6
console.log(queueStack.pop()); // 移除5
console.log(queueStack.top()); // 棧頂是 2
}
複製程式碼
如果你有其他好的方法,歡迎留言
3. 總結
我們利用基礎的陣列api實現了佇列和棧的功能,再來回顧下(敲黑板,劃重點了)
- 佇列最大的特點就是先進先出,主要的兩個操作是入隊和出隊,和棧一樣,是個受限制的資料結構。
- 佇列既可以用陣列實現,也可以用連結串列實現,用陣列實現的叫順序佇列,用連結串列實現的叫鏈式佇列(後續更新)
- 棧最大的特點就是先進後出,主要的兩個操作是出棧和入棧,也是一個受限制的資料結構。
- 和佇列一樣,既可以用陣列實現,也可以用連結串列實現。不管基於陣列還是連結串列,入棧、出棧的時間複雜度都為 O(1),佇列也是如此。
當然還有其他的佇列和棧,我這裡只介紹到了基礎的實現。這個硬骨頭,還需慢慢啃。
4. 重點
如果有錯誤或者錯別字,還請給我留言指出,謝謝。