用登山来比喻回溯,比如从山脚下找一条爬上山顶的路,起初有好几条道可走,当选择一条道走到某处时,又有几条岔道可供选择,只能选择其中一条道往前走,若能这样子顺利爬上山顶则罢了,不然走到一条绝路上时或者这条路上有一坨屎,咱们只好返回到最近的一个路口,从新选择另外一条没走过的道往前走。若是该路口的全部路都走不通,只得从该路口继续回返。照此规则走下去,要么找到一条到达山顶的路,要么最终试过全部可能的道,没法到达山顶。
回溯本质上是一种穷举。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; }