JavaScript 數據結構之隊棧互搏

今天稍微停下前進的腳步,來看下隊棧的左右互搏術。 前兩天學習了隊列和棧之後,今天就能夠試着來用兩個棧實現隊列的功能或者用兩個隊列來實現棧的功能。api

1. 用兩個棧實現一個隊列

1.1 題目分析

棧是先進後出,隊列是先進先出,但能夠用兩個棧來模擬一個隊列的功能,來實現隊列中主要的 enqueue,dequeue, head 方法。數組

1.2 思路分析

咱們所學的每一種數據結構,本質上都是對數據如何存儲和使用的研究,這就必然涉及到增刪改查,那麼考慮實現這些方法時,咱們優先考慮如何實現數據的增長,只有存在了數據,纔可以後續的操做。 因此如何實現隊列中的增長數據方法 enqueue 呢?bash

  • 給兩個棧分別命名爲 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。ui

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啦。 接下來讓咱們繼續修煉,用隊列實現棧吧!this

2. 用兩個隊列實現一個棧

2.1 題目分析

隊列是先進先出,棧是先進後出,(不斷重複這兩個知識點) 但能夠用兩個隊列來模擬一個棧的功能,來實現棧中主要的 push,pop, top 方法。spa

你可能會想到利用上邊的套路來實現這個需求,可是最後的結果你會發現是不正確的。由於 把 stack1 的元素移除到 stack2 中,此時的兩個棧中的數據就首尾交換了,而若是此處換成隊列 this.queue2. enqueue(this.queue1. dequeue()), 你會發現因爲隊列的特性,此時的兩個隊列仍是同樣的,首尾並無交換。code

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
    }
複製代碼

若是你有其餘好的方法,歡迎留言

總結

咱們利用基礎的數組 api 實現了隊列和棧的功能,再來回顧下(敲黑板,劃重點了)

  • 隊列最大的特色就是先進先出,主要的兩個操做是入隊和出隊,和棧同樣,是個受限制的數據結構。
  • 隊列既能夠用數組實現,也能夠用鏈表實現,用數組實現的叫順序隊列,用鏈表實現的叫鏈式隊列(後續更新)
  • 棧最大的特色就是先進後出,主要的兩個操做是出棧和入棧,也是一個受限制的數據結構。
  • 和隊列同樣,既能夠用數組實現,也能夠用鏈表實現。無論基於數組仍是鏈表,入棧、出棧的時間複雜度都爲 O(1),隊列也是如此。

固然還有其餘的隊列和棧,我這裏只介紹到了基礎的實現。這個硬骨頭,還需慢慢啃。

重點

若是有錯誤或者錯別字,還請給我留言指出,謝謝。

相關文章
相關標籤/搜索