1. 程式人生 > >JavaScript資料結構之隊棧互搏

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. 重點

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