讀後感:資料結構與演算法JavaScript描述

ESnail發表於2019-05-19

本書看完,對常見的資料結構與演算法從概念上有了更深入的理解。

書中關於陣列、棧和佇列、連結串列、字典、雜湊、集合、二叉樹、圖、排序、檢索、動態規劃、貪心演算法都有詳細的介紹。算是一本不錯的學習書籍。

棧和佇列:都可以通過陣列來模擬。棧的規則是先進後出,佇列的規則是先進先出

連結串列:有雙向與單向連結串列之分,預設有一個頭指標,指向下一個節點,就像鏈條一樣。

陣列 vs 連結串列:

  • 陣列查詢速度快:陣列能夠通過索引快速找到一個元素,高效快速。而連結串列沒有索引,查詢不是很便捷。
  • 連結串列增加、刪除效能高:陣列增加、刪除一個元素,後面的元素都需要移動一個位置,資料量大時,改前面的資料,很耗效能。而連結串列只需改動節點的指向索引,非常高效。

字典:簡單理解就是key/value的鍵值對儲存的物件。

雜湊:基於陣列設計,長度預定。藉助雜湊函式將每個鍵值對映為一個唯一的陣列索引。

集合:js中的map、set。

二叉樹:非線性的資料結構,以分層的方式儲存。用來儲存具有層級關係的資料。比如檔案系統。具有根節點、左子樹、右子樹、葉子節點。樹的層數就是樹的深度。樹的遍歷:

  • 中序:按照節點上的鍵值,升序訪問所有節點
  • 先序:先訪問根節點,然後以同樣的方式訪問左子樹和右子樹。
  • 後序:先訪問葉子節點,從左子樹到右子樹,再到根節點。

圖:由邊的集合及頂點的集合組成。地圖就是一種圖。如果一個圖的頂點是有序的,則稱之為有向圖,反之為無向圖。

常見的排序:氣泡排序、選擇排序、插入排序

高階排序:希爾排序、歸併排序。

檢索:二分查詢演算法,比排序查詢演算法高效。

動態規劃

使用遞迴方案能解決的問題,都能夠使用動態規劃技巧來解決。

動態規劃:解決相似子問題,並儲存子問題的解,通過子問題的解從而解決整個問題。子問題的解通常儲存在陣列中便於訪問

動態規劃,聽過它的鼎鼎大名,但一直沒深入瞭解過。

書中的例子:

1.計算裴波那契數列

斐波那契數列:0,1,1,2,3,5,8,13...

該序列是由前兩項數值相加而成的

遞迴實現:效率低,有太多值在遞迴呼叫中被重新計算。

function recurFib (n) {
  if (n < 2) {
    return n;
  }
  return recurFib(n - 1) + recurFib(n - 2);
}

動態規劃實現:將陣列的每個元素賦值為前兩個元素之和,迴圈結束,最後一個元素值即為最終計算得到的斐波那契數值

function dynFib(n) {
  var val = [];
  for (var i = 0; i <= n; i++) { // 0 - n:含 n+1 個
    val[i] = 0;
  }
  
  if (n == 1 || n == 2) {
    return 1;
  }
  val[1] = 1;
  val[2] = 2;
  for (var i = 3; i <= n; i++) {
    val[3] = val[i-1] + val[i-2];
  }
  return val[n-1];
}

迭代實現:不儲存中間結果

function iterFib (n) {
  var last = 1;
  var nextLast = 1;
  var result = 1;
  for (var i = 2; i < n; i++) {
    result = last + nextLast;
    nextLast = last;
    last = result;
  }
  return result;
}

2.尋找兩個字串的最長公共子串

動態規劃:使用一個二維陣列儲存兩個字串相同位置的字元比較結果。初始陣列每一個元素被設定為0,每次在這兩個陣列的相同位置發現了匹配,就將該陣列對應行和列的元素加1,否則保持為0

function lcs (word1, word2) {
  // 初始化二維陣列,每項為0
  var max = 0;
  var index = 0;
  var lcsarr = new Array(word1.length + 1); 
  for (var i = 0; i <= word1.length + 1; i++) {
    lcsarr[i] = new Array(word2.length + 1);
    for (var j = 0; j <= word2.length + 1; j++) {
      lcsarr[i][j] = 0;
    }
  }

  // 儲存字元匹配的記錄,如果兩個字串相應位置的字元進行了匹配,當前陣列元素的值將被設定為前一次迴圈中陣列元素儲存的值加1,最後如果變數max的值比現在儲存在陣列中的當前元素小,max的值將被賦值給這個元素,變數index的值將被設定為i的當前值。這兩個變數將在函式的最後一部分用於確定從哪裡開始獲取最長公共子串。
  for (var i = 0; i <= word1.length; i++) {
    for (var j = 0; j <= word2.length; i++) {
      if (i == 0 || j == 0) {
        lcsarr[i][j] = 0;
      } else {
        if (word1[i - 1] == word2[j - 1]) {
          lcsarr[i][j] = lcsarr[i - 1][j - 1] + 1;
        } else {
          lcsarr[i][j] = 0;
        }
      }
      if (max < lcsarr[i][j]) {
        max = lcsarr[i][j];
        index = i;
      }
    }
  }

  // 確認從哪裡開始構建這個最長公共子串。以變數index減去變數max的差值作為起始點,以變數max的值作為終點。
  var str = '';
  if (max == 0) {
    return '';
  } else {
    for (var i = index - max; i <= max; i++) {
      str += word2[i];
    }
    return str;
  }
}

書中還有關於如何用遞迴與動態規劃解決揹包問題,甚至用貪心演算法解決部分揹包問題。

揹包問題:有一個保險箱,保險箱中的物品規格和價值不同。你需要將保險箱中的寶物放入一個你的揹包,希望揹包裝進的寶貝價值最大。如:保險箱中有5件物品,尺寸:3,4,7,8,9,價值:4,5,10,11,13,揹包的容積為16。最優解是:選取第三件和第五件,總尺寸是16,總價值是23。

如果感興趣的,可以翻開此書看看。

相關文章