用登山來比喻回溯,比如從山腳下找一條爬上山頂的路,起初有好幾條道可走,當選擇一條道走到某處時,又有幾條岔道可供選擇,只能選擇其中一條道往前走,若能這樣子順利爬上山頂則罷了,不然走到一條絕路上時或者這條路上有一坨屎,咱們只好返回到最近的一個路口,從新選擇另外一條沒走過的道往前走。若是該路口的全部路都走不通,只得從該路口繼續回返。照此規則走下去,要麼找到一條到達山頂的路,要麼最終試過全部可能的道,沒法到達山頂。
回溯本質上是一種窮舉。html
還有一些愛混淆的概念:遞歸,回溯,DFS。這些都是一個事兒的不一樣方面。如下以回溯統稱,由於這個詞聽上去很文雅。node
判斷回溯很簡單,拿到一個問題,你感受若是不窮舉一下就無法知道答案,那就能夠開始回溯了。
通常回溯的問題有三種:express
Find all paths to success 求全部解數組
理解回溯:給一堆選擇, 必須從裏面選一個. 選完以後我又有了新的一組選擇. This procedure is repeated over and over until you reach a final state. If you made a good sequence of choices, your final state is a goal state; if you didn't, it isn't.函數
回溯能夠抽象爲一棵樹,咱們的目標能夠是找這個樹有沒有good leaf,也能夠是問有多少個good leaf,也能夠是找這些good leaf都在哪,也能夠問哪一個good leaf最好,分別對應上面所說回溯的問題分類。
good leaf都在leaf上。good leaf是咱們的goal state,leaf node是final state,是解空間的邊界。優化
對於第一類問題(問有沒有解),基本都是長着個樣子的,理解了它,其餘類別迎刃而解:code
boolean solve(Node n) { if n is a leaf node { if the leaf is a goal node, return true else return false } else { for each child c of n { if solve(c) succeeds, return true } return false } }
請讀如下這段話以加深理解:
Notice that the algorithm is expressed as a boolean function. This is essential to understanding the algorithm. If solve(n) is true, that means node n is part of a solution--that is, node n is one of the nodes on a path from the root to some goal node. We say that n is solvable. If solve(n) is false, then there is no path that includes n to any goal node.htm
還不懂的話請通讀全文吧:Backtracking - David Matuszek遞歸
關於回溯的三種問題,模板略有不一樣,
第一種,返回值是true/false。
第二種,求個數,設全局counter,返回值是void;求全部解信息,設result,返回值void。
第三種,設個全局變量best,返回值是void。ci
第一種:
boolean solve(Node n) { if n is a leaf node { if the leaf is a goal node, return true else return false } else { for each child c of n { if solve(c) succeeds, return true } return false } }
第二種:
void solve(Node n) { if n is a leaf node { if the leaf is a goal node, count++, return; else return } else { for each child c of n { solve(c) } } }
第三種:
void solve(Node n) { if n is a leaf node { if the leaf is a goal node, update best result, return; else return } else { for each child c of n { solve(c) } } }
1.給個n,問有沒有解;
2.給個n,有幾種解;(Leetcode N-Queens II)
3.給個n,給出全部解;(Leetcode N-Queens I)
怎麼作:一行一行的放queen,每行嘗試n個可能,有一個可達,返回true;都不可達,返回false.
邊界條件leaf:放完第n行 或者 該放第n+1行(出界,返回)
目標條件goal:n行放滿且isValid,即目標必定在leaf上
helper函數:
boolean solve(int i, int[][] matrix)
在進來的一瞬間,知足property:第i行尚未被放置,前i-1行放置完畢且valid
solve要在給定的matrix上試圖給第i行每一個位置放queen。
public static boolean solve1(int i, List<Integer> matrix, int n) { if (i == n) { if (isValid(matrix)) return true; return false; } else { for (int j = 0; j < n; j++) { matrix.add(j); if (isValid(matrix)) { //剪枝 if (solve1(i + 1, matrix, n)) return true; } matrix.remove(matrix.size() - 1); } return false; } }
怎麼作:一行一行的放queen,每行嘗試n個可能。這回由於要找全部,返回值就沒有了意義,用void便可。在搜索時,若是有一個可達,仍要繼續嘗試;每一個子選項都試完了,返回.
邊界條件leaf:放完第n行 或者 該放第n+1行(出界,返回)
目標條件goal:n行放滿且isValid,即目標必定在leaf上
helper函數:
void solve(int i, int[][] matrix)
在進來的一瞬間,知足property:第i行尚未被放置,前i-1行放置完畢且valid
solve要在給定的matrix上試圖給第i行每一個位置放queen。
這裏爲了記錄解的個數,設置一個全局變量(static)int是比較efficient的作法。
public static void solve2(int i, List<Integer> matrix, int n) { if (i == n) { if (isValid(matrix)) count++; return; } else { for (int j = 0; j < n; j++) { matrix.add(j); if (isValid(matrix)) { //剪枝 solve2(i + 1, matrix, n); } matrix.remove(matrix.size() - 1); } } }
怎麼作:一行一行的放queen,每行嘗試n個可能。返回值一樣用void便可。在搜索時,若是有一個可達,仍要繼續嘗試;每一個子選項都試完了,返回.
邊界條件leaf:放完第n行 或者 該放第n+1行(出界,返回)
目標條件goal:n行放滿且isValid,即目標必定在leaf上
helper函數:
void solve(int i, int[][] matrix)
在進來的一瞬間,知足property:第i行尚未被放置,前i-1行放置完畢且valid
solve要在給定的matrix上試圖給第i行每一個位置放queen。
這裏爲了記錄解的具體狀況,設置一個全局變量(static)集合是比較efficient的作法。
固然也能夠把結果集合做爲參數傳來傳去。
public static void solve3(int i, List<Integer> matrix, int n) { if (i == n) { if (isValid(matrix)) result.add(new ArrayList<Integer>(matrix)); return; } else { for (int j = 0; j < n; j++) { matrix.add(j); if (isValid(matrix)) { //剪枝 solve3(i + 1, matrix, n); } matrix.remove(matrix.size() - 1); } } }
上面的例子用了省空間的方法。
因爲每行只能放一個,一共n行的話,用一個大小爲n的數組,數組的第i個元素表示第i行放在了第幾列上。
Utility(給一個list判斷他的最後一行是否和前面衝突):
public static boolean isValid(List<Integer> list){ int row = list.size() - 1; int col = list.get(row); for (int i = 0; i <= row - 1; i++) { int row1 = i; int col1 = list.get(i); if (col == col1) return false; if (row1 - row == col1 - col) return false; if (row1 - row == col - col1) return false; } return true; }