1. 程式人生 > >資料結構和演算法躬行記(5)——回溯演算法

資料結構和演算法躬行記(5)——回溯演算法

  回溯演算法(backtracking)是一個類似列舉的搜尋嘗試過程,在尋找問題解的過程中,當發現不滿足求解條件時,就退回一步,嘗試其它路徑,該演算法的時間複雜度都不會低於 O(N!),複雜的例題包括正則表示式匹配、解數獨等。

  在《回溯演算法詳解》一文中提到,解決一個回溯問題,實際上就是一個決策樹的遍歷過程,需要思考三個問題:

  (1)路徑:已經做出的選擇。

  (2)選擇列表:當前可以做的選擇。

  (3)結束條件:到達決策樹底層,無法再做選擇的條件。

  下面是改編過的演算法通用結構。

function backtrack(路徑, 選擇列表):
    if 滿足結束條件
        console.log(路徑)
        return
    for 選擇 of 選擇列表
        做選擇
        backtrack(路徑, 選擇列表)
        撤銷選擇

  面試題12 矩陣路徑和麵試題13 機器人運動範圍。在二維方格或矩陣的運動可用回溯法解決。

一、N皇后

  N皇后是一道經典的回溯演算法題,將 n 個皇后放置在 n×n 的棋盤上,使皇后彼此之間不能相互攻擊,即每個棋子所在的行、列、對角線都不能有另一個棋子。

  在下面的示例中,N是皇后的數量,backtrack()函式是回溯過程(如下所列),isValid()函式判斷是否符合選中條件。

  (1)從第一個 row=0 開始。

  (2)迴圈列並且試圖在每個 column 中放置皇后。

  (3)如果方格 (row, column) 在攻擊範圍內,那麼跳過。

  (4)在 (row, column) 方格上放置皇后,繼續尋找下一個位置。

  (5)判斷 row 是否和皇后數量相同。

const N = 4;
function backtrack(route, row) {
  if (row == N) {                //結束條件
    console.log(route);
    return;
  }
  for (let column = 0; column < N; column++) {
    if (!isValid(route, row, column))
      continue;
    route[row] = column;        //做選擇
    backtrack(route, row + 1);  //下一步
    route[row] = null;          //撤銷選擇(可省略)
  }
}

//從下往上 判斷row行column列放置是否合適
function isValid(route, row, column) {
  let leftup = column - 1,
    rightup = column + 1;
  for (let i = row - 1; i >= 0; i--) {        // 逐行往上考察每一行
    if (route[i] == column)                   // 第i行的column列有棋子
      return false;     
    if (leftup >= 0) {            
      if (route[i] == leftup)                 // 考察左上對角線:第i行leftup列有棋子
        return false;
    }
    if (rightup < N) {
      if (route[i] == rightup)                // 考察右上對角線:第i行rightup列有棋子
        return false;
    }
    leftup--;
    rightup++;
  }
  return true;
}

二、0-1揹包

  有一個揹包,揹包總的承載重量是 Wkg。現在有 n 個物品,假設每個物品的重量都不相等,並且不可分割。期望選擇幾件物品,裝載到揹包中。在不超過揹包容量的前提下,如何讓揹包中物品的總重量最大?

  把物品依次排列,對於物品選擇裝或不裝,然後遞歸餘下的物品,如下所示。

let max = Number.MIN_VALUE,
    W = 100;
function backtrack(route, goods) {
  let weight = route.length ? route.reduce((acc, cur) => acc += cur) : 0;
  if (weight == W || route.length == goods.length) {    //結束條件
    if (weight > max && weight <= W) {
      max = weight;
    }
    console.log(route);
    return;
  }
  for (let i = 0; i < goods.length; i++) {
    if(weight + goods[i] > W || route.indexOf(goods[i]) > -1)
      continue;
    route.push(goods[i]);            //做選擇
    backtrack(route, goods);
    route.pop();                     //撤銷選擇
  }
}

三、全排列

  全排列是指輸出給定數字序列的全部可能的排列,假設序列中的數字都是唯一的,利用回溯演算法枚舉出所有排列,如下所示。

function backtrack(route, nums) {
  if (route.length == nums.length) {        //結束條件
    console.log(route);
    return;
  }
  for (let i = 0; i < nums.length; i++) {
    if (route.indexOf(nums[i]) > -1)
      continue;
    route.push(nums[i]);        //做選擇
    backtrack(route, nums);
    route.pop();                //撤銷選擇
  }
}

  面試題17 列印從 1~n 位的數。將問題轉換成數字排列,用遞迴實現。

&n