JavaScript數據結構之隊列

接上篇-數據結構之棧html

數據結構之---隊列

1.隊列的定義

隊列是一種特殊的線性表,特殊之處在於它只容許在表的前端(front)進行刪除操做,而在表的後端(end)進行插入操做,和棧同樣,隊列是一種操做受限制的線性表。進行插入操做的端稱爲隊尾,進行刪除操做的端稱爲隊首。前端

隊列的數據元素又稱爲隊列元素。在隊列中插入一個隊列元素稱爲入隊,從隊列中刪除一個隊列元素稱爲出隊。由於隊列只容許在一端插入,在另外一端刪除,因此只有最先進入隊列的元素才能最早從隊列中刪除,故隊列的特性爲 先進先出 (First-In-First-Out,FIFO)es6

請看下面的圖解 後端

隊列添加新的元素,左側是隊列的頭部,右側是隊列的尾部,新的元素若是想進入隊列,只能從尾部進入。
隊列移除元素,左側是隊列的頭部,右側是隊列的尾部, 若是想要出隊列,只能從隊列的頭部出去
平常生活中,排隊就是典型的隊列結構,先到的先被服務,後來的在隊尾等着,直到輪到他爲止(固然,特殊狀況除外)。好比說其餘場景 提交操做系統執行的一系列進程、打印任務池等,一些仿真系統用隊列來模擬銀行或雜貨 店裏排隊的顧客。

2.隊列的實現

從數據存儲的角度看,實現隊列有兩種方式,一種是以數組作基礎,一種是以鏈表作基礎,數組是最簡單的實現方式,本文以基礎的數組來實現隊列。數組

隊列的操做包括建立隊列、銷燬隊列、入隊、出隊、清空隊列、獲取隊頭元素、獲取隊列的長度。bash

咱們定義如下幾個隊列的方法:數據結構

  • enqueue 從隊尾添加一個元素(新來一個辦業務的人,排在了隊尾)
  • dequeue 從隊首刪除一個元素(隊伍最前面的人,辦完了業務,離開了)
  • head 返回隊首的元素(後邊的人好奇看一下,隊伍最前面的人是誰)
  • tail 返回隊尾的元素(前邊的人好奇看一下,隊伍最後面的人是誰)
  • size 返回隊列的大小(營業員數一下隊伍有多少人)
  • isEmpty 返回隊列是否爲空(營業員查看當前是否是有人在排隊)
  • clear 清空隊列(此窗口暫停營業,你們撤了吧)

而後咱們利用es6的class的實現以上的方法 新建一個queue.js文件socket

class Queue {
  constructor() {
    this.items = []; // 存儲數據
  }
  enqueue(item) { // 向隊尾添加一個元素
    this.items.push(item);
  }
  dequeue() { // 刪除隊首的一個元素
    return this.items.shift();
  }
  head() { // 返回隊首的元素
    return this.items[0];
  }
  tail() { // 返回隊尾的元素
    return this.items[this.items.length - 1];
  }
  size() { // 返回隊列的元素
    return this.items.length;
  }
  isEmpty() { // 返回隊列是否爲空
    return this.items.length === 0;
  }
  clear() { // 清空隊列
    this.items = [];
  }
}

複製代碼
3.隊列的應用

記住兩點:函數

  • 棧的特性是先進後出(聯想:羽毛球桶)
  • 隊列的特性是先進先出(聯想:排隊)
3.1 約瑟夫環問題

有一個數組存放了100個數據0-99,要求每隔兩個數刪除一個數,到末尾時再循環至開頭繼續進行,求最後一個被刪除的數字。post

好比說:有十個數字:0,1,2,3,4,5,6,8,9,每隔兩個數刪除一個數,就是2 5 8 刪除,若是隻是從0到99每兩個數刪除一個數,其實挺簡單的,可是咱們還得考慮到末尾的時候還有再重頭開始,還得考慮刪除掉的元素從數組中刪除。那咱們若是隊列的話,就比較簡單了

3.1.2 思路分析

  • 先將這100個數據放入隊列,用while循環,終止的條件是隊列裏只有一個元素。
  • 定義index變量從0開始計數,從隊列頭部刪除一個元素,index + 1
  • 若是index%3 === 0 ,說明這個元素須要被移除隊列,不然的話就把它添加到隊列的尾部

通過while循環後,不斷的有元素出隊列,最後隊伍中只會剩下一個被刪除的元素

3.1.3 看代碼實現

// 每隔兩個數刪除一個數
    {
      var arr = []; // 準備0-99  100個數據
      for (var i = 0; i < 100; i++) {
        arr.push(i);
      }
      function delRang(arr) {
        var queue = new Queue(); // 調用以前實現Queue類
        var len = arr.length;
        for (var i = 0; i < len; i++) {
          queue.enqueue(i); // 將數據存入隊列
        }
        var index = 0;
        while (queue.size() !== 1) { // 循環判斷隊列裏大小否爲還剩下1個
          var item = queue.dequeue(); // 出隊一個元素,根據當前的index來判斷是否須要移除
          index += 1;
          if (index % 3 !== 0) {
            queue.enqueue(item); // 不是的話,則添加到隊尾,繼續循環
          }
        }
        console.log(queue.head()); // 90
        return queue.head(); // 返回最後一個元素
      }
      delRang(arr);
    }
複製代碼

是否是感受使用隊列很簡單呢,接下來再看幾個小練習

3.2 斐波那契數列

3.2.1 題目介紹

什麼是斐波那契數列: 斐波那契數列(Fibonacci sequence),又稱黃金分割數列、因數學家列昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖爲例子而引入,故又稱爲「兔子數列」,指的是這樣一個數列:一、一、二、三、五、八、1三、2一、3四、……這個數列從第3項開始,每一項都等於前兩項之和。在數學上,斐波納契數列以以下被以遞歸的方法定義:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)來源:斐波那契數列——百度百科

3.2.2 咱們先考慮使用普通的方法實現 -- 遞歸 遞歸版 代碼實現

function Fibonacci (n) {
  if ( n <= 2 ) {return 1};
  return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 55
Fibonacci(100) // 堆棧溢出
Fibonacci(500) // 堆棧溢出
複製代碼

由上可見,遞歸很是消耗內存,由於須要同時保存成千上百個調用幀,很容易發生「棧溢出」錯誤。可是也有解決的辦法,採用尾遞歸優化。

函數調用自身,稱爲遞歸;若是尾調用自身,就稱爲尾遞歸。 對於尾遞歸來講,因爲只存在一個調用棧,因此永遠不會發生「棧溢出」錯誤。

尾遞歸版 代碼實現

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 2 ) {return ac2};
  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100) // 354224848179262000000
Fibonacci2(1000) // 4.346655768693743e+208
複製代碼

上面代碼雖然簡潔,可是不易想到

3.2.3 那接下來咱們用隊列實現一遍 思路分析

  • 須要先將兩個1 添加到隊列中
  • 定義index來計數,採用while循環,終止條件是 index < n - 2(由於每次遍歷咱們只保留2個元素在隊列中)
  • 使用dequeue方法移除隊列頭部的元素,標記爲 numDel;
  • 使用head方法獲取此時頭部的元素,標記爲numHead;
  • 使用enqueue方法將前二者的和從尾部放入隊列中
  • index + 1

當循環結束後,隊列裏面只有兩個元素,用dequeue方法移除頭部元素後,再用head方法獲取的頭部元素就是最終的結果,並且此方法不會產生「棧溢出」錯誤。

隊列版 代碼實現

{
      function fibonacci(n) {
        if (n <= 2) return 1;
        var queue = new Queue();
        // 先存入序列的前兩個值
        queue.enqueue(1);
        queue.enqueue(1);
        var index = 0;
        while (index < n - 2) {
          var delItem = queue.dequeue(); // 移除隊列的頭部元素
          var headItem = queue.head(); // 獲取隊列頭部元素(由於上一步已經將頭部元素移除)
          var resNum = delItem + headItem;
          queue.enqueue(resNum); // 將二者之和存入隊列
          index += 1;
        }
        queue.dequeue();
        return queue.head();
      }
      console.log("fibonacci", fibonacci(10)); // 55
      console.log("fibonacci", fibonacci(100)); // 354224848179262000000
    }
複製代碼
3.3 打印楊輝三角

3.3.1 題目分析 所謂楊輝三角,你們確定都不會陌生,以下圖所示 楊輝三角——百度百科介紹 計算的方式:f[i][j] = f[i-1][j-1] + f[i-1][j], i 表明行數,j表明一行的第幾個數,若是j= 0 或者 j = i ,則 f[i][j] = 1。

3.3.2 思路分析

  • 楊輝三角中的每一行,都依賴於上一行,假設如今隊列裏已經存儲了第n-1行的數據,那麼輸出第n行時,只須要將隊列裏的數據依次出隊列,進行計算獲得下一行的數值並講計算所得存儲到隊列中
  • 而後咱們須要兩層for循環,將n-1行和n行的數據分開打印;有上圖能夠得出規律,n行只有n個數,因此咱們就可使用for循環控制enqueue的次數,n次結束後,隊列裏存儲的就是計算好的第n+1行的數據

3.3.3 代碼實現

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>打印楊輝三角</title>
  </head>
  <body>
    <script src="./queue.js"></script>
    <script>
      // 楊輝三角
      {
        function yangHui(n) {
          var queue = new Queue();
          queue.enqueue(1); // 先在隊列中存儲第一行的數據
          for (var i = 1; i <= n; i++) { // 第一層循環控制層數
            var line = "";
            var pre = 0;
            for (var j = n; j > i; j--) { // 打印空格
              document.write("&nbsp;");
            }
            for (var j = 0; j < i; j++) { // 第二層控制當前層的數據
              var item = queue.dequeue();
              var value = item + pre; // 計算下一行的值
              pre = item;
              line += item + " ";
              queue.enqueue(value);
            }
            queue.enqueue(1); // 將每層的最後一個數值 1 存入隊列中
            document.write(line + "<br />");
          }
        }
        yangHui(10);
      }
    </script>
  </body>
</html>
複製代碼
4.隊列總結

使用隊列的例子還有不少,好比逐層打印一顆樹上的節點,還有消息通信使用的socket,當大量客戶端向服務端發起鏈接,而服務端擁擠時,就會造成隊列,先來的先處理,後來的後處理,當隊列滿時,新來的請求直接拋棄掉。 數據結構在系統設計中的應用很是普遍,只是咱們水平達不到那個級別,知道的太少,但若是能理解並掌握這些數據結構,那麼就有機會在工做中使用它們並解決一些具體的問題,當咱們手裏除了錘子還有電鋸時,那麼咱們的眼裏就不僅是釘子,解決問題的思路也會更加開闊。

5.參考

阮一峯-函數的擴展

相關文章
相關標籤/搜索