徹底理解回溯法的精要


給定一個沒有重復數字的序列,返回其所有可能的全排列。
示例:

輸入: [1,2,3]
輸出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

問題分析

使用什么方法?

全排列很明顯使用回溯法來進行解答

什么是回溯法?

回溯法(探索與回溯法)是一種選優搜索法,又稱為試探法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法,而滿足回溯條件的某個狀態的點稱為“回溯點”。

怎么使用回溯法?

運用回溯法解題的關鍵要素有以下三點:

  1. 針對給定的問題,定義問題的解空間;
  2. 確定易於搜索的解空間結構;
  3. 深度優先方式搜索解空間,並且在搜索過程中用剪枝函數避免無效搜索。

什么是深度優先搜索?

深度優先搜索(縮寫DFS)有點類似廣度優先搜索,也是對一個連通圖進行遍歷的算法。它的思想是從一個頂點V0開始,沿着一條路一直走到底,如果發現不能到達目標解,那就返回到上一個節點,然后從另一條路開始走到底,這種盡量往深處走的概念即是深度優先的概念。

代碼模板是什么樣子的?

void BackTrace(int t) {
    if(t>n)
        Output(x);
    else
    for(int i = f (n, t); i <= g (n, t); i++ ) {
        x[t] = h(i);
        if(Constraint(t) && Bound (t))
        BackTrace(t+1);
    }
}

其中,t 表示遞歸深度,即當前擴展結點在解空間樹中的深度;n 用來控制遞歸深度,即解空間樹的高度。當 t>n時,算法已搜索到一個葉子結點,此時由函數Output(x)對得到的可行解x進行記錄或輸出處理

f(n, t)g(n, t)分別表示在當前擴展結點處未搜索過的子樹的起始編號和終止編號;h(i)表示在當前擴展結點處x[t] 的第i個可選值;函數 Constraint(t)Bound(t)分別表示當前擴展結點處的約束函數和限界函數。若函數 Constraint(t)的返回值為真,則表示當前擴展結點處x[1:t] 的取值滿足問題的約束條件;否則不滿足問題的約束條件。若函數Bound(t)的返回值為真,則表示在當前擴展結點處x[1:t] 的取值尚未使目標函數越界,還需由BackTrace(t+1)對其相應的子樹做進一步地搜索;否則,在當前擴展結點處x[1:t]的取值已使目標函數越界,可剪去相應的子樹。

回溯法的具體實施

class Solution {
    public List<List<Integer>> permute(int[] nums) {
      //LeetCode代碼模板  
    }
}

step 1 定義問題的解空間

什么是解空間?

應用回溯法求解問題時,首先應明確定義問題的解空間,該解空間應至少包含問題的一個最優解。例如,對於有n種物品的 0-1 背包問題,其解空間由長度為n0-1 向量組成,該解空間包含了對變量的所有可能的0-1 賦值。當n=3時,其解空間是{ (0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1) }
在定義了問題的解空間后,還需要將解空間有效地組織起來,使得回溯法能方便地搜索整個解空間,通常將解空間組織成樹或圖的形式。例如,對於n= 30-1 背包問題,其解空間可以用一棵完全二叉樹表示,從樹根到葉子結點的任意一條路徑可表示解空間中的一個元素,如從根結點A到結點J的路徑對應於解空間中的一個元素(1, 0, 1)

定義本題的解空間

全排列問題,因為輸入數組的長度為n = nums.length,解空間就是一個森林:

這里需要一個森林的圖
假設n=4nums[]={1,2,3,4}則解空間應該是
第一層:1 2 3 4
第二層:12 13 14 /21 23 24/31 32 34
第三層:123 124/132 134/213 214/231 234/241 243/312 314/.....
第四層:略

確定易於搜索的解空間結構

解空間主要對應的是子集樹和排列樹,依據題意進行選擇。(根據題意畫個圖,就知道了)

什么是子集樹???

子集樹是一個數學學科詞匯,屬於函數類,當所給問題是從n個元素的集合S中找出S滿足某種性質的子集時,相應的解空間稱為子集樹。
當所給問題是從n個元素的集合S中找出S滿足某種性質的子集時,相應的解空間稱為子集樹。例如:n個物品的0-1背包問題所相應的解空間是一棵子集樹,這類子集樹通常有2^n個葉結點,其結點總數為(2^(n+1))-1。遍歷子集樹的算法通常需O(2^n)計算時間。

什么是排列樹??

當所給問題是確定n個元素滿足某種性質的排列時,相應的解空間樹稱為排列樹。排列樹通常有n!個葉子節點。因此遍歷排列樹需要O(n!)的計算時間。

上面已經確定,要將解空間構建成子集樹的形式

step 2 回溯法的精髓

回溯的精髓

退回原狀態
如何回退是回溯的精髓,什么時候回退
就本題而言,第一躺全排列應該是1->2->3->4 ,當走到最后一步4之后,應該回退一步到1->2->3因為3只有一個分支4,再回退一步到1->2,然后滿足了約束函數可以進行下一步1->2->4;
對於本題,回退到方法在於,標記未被訪問的數組下標,回退則重制標記
因此可以使用一個visited[]數組,數組的長度為nums.length,被訪問則對應的下標標記為true,否則標記為false

step 3 回溯函數的設計

void BackTrace(int t)只傳遞一個參數的話顯然是無法滿足本題的,因為本題包含了一下5個需要傳遞的參數:

  1. visited[] 數組;
  2. t 遞歸深度;
  3. List<List<Integer>> output 保存所有解的大容器
  4. List<Integer> save 保存解的小容器
  5. nums[]原始數據

因此,BackTrace應設計為:

public static void BackTrace( List<Integer> save, List<List<Integer>> out, boolean visited[], int nums[]) {
        if (save.size() == nums.length) {
            out.add(new ArrayList<>(save));
            return;
        } else
            for (int i = 0; i < nums.length; i++) {
                if (visited[i]) continue;
                visited[i] = true;
                save.add(nums[i]);
                BackTrace( save, out, visited, nums);
                save.remove(save.size() - 1);
                visited[i] = false;
            }
    }

怎么寫出這段代碼需要結合前面的內容反復的思考 =-= 我想了好久才理清楚回溯的思路

回溯法的延伸

子集問題
題目:

給定一組不含重復元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重復的子集。
示例:

輸出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

從上題中我們可以得出結論,這仍然是一道需要使用回溯法的題目。

解空間與解空間結構

很明顯這是一個子集數的解空間結構

假設n=3nums[]={1,2,3}則解空間應該是
第一層:1 2 3
第二層:12 13/21 23/31 32
第三層:123 132/213 231/312 321/

關鍵性問題

  1. 通過什么方法回退?
  2. 約束條件是什么?
  3. 去除重復對象
檢測重復

檢測重復首先想到的會是哈希表HashMap.因此每一次添加都應該在添加之前查找,如果找到重復則不存入;

約束條件是什么

約束條件應該還是當遍歷到最后一個元素時退出?

通過什么方法回退?

由於集合的特殊性。不需要回退;

函數的設計:

public static void BackTrack(int t,int[] nums, List<List<Integer>> out, List<Integer> save) {

        out.add(new ArrayList<>(save));

        for (int i = t; i < nums.length; i++) {

            save.add(nums[i]);

            BackTrack(i+1,nums, out, save);

            save.remove(save.size()-1);
        }
    }
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM