回溯法(DFS).
(1) 回溯法是一种 选优搜索法, 按选优条件向前搜索,以达到目标。 当探索到某一步时,发现原先选择 并不优或达不到目标, 就 退回一步重新选择别的路径, 这种走不通就退回再走的技术为 回溯法. 而满足回溯条件的某个状态的点称为 “回溯点”.
(2) 在包含问题的所有解的解空间树中,按照 深度优先搜索 的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯.
(3) 若用回溯法求问题的 所有解, 要回溯到根, 且根结点的所有可行的子树都要已被搜索遍才结束. 而若使用回溯法求 任一个解, 只要搜索到问题的一个解就可以结束.
1 典型例题: 假如有 编号为的 1 ~ 3的3张扑克牌和编号为1 ~ 3的3个盒子, 现在需要将3张牌分别放到3个盒子中去, 且每个盒子只能放一张牌, 一共有多少种不同的放法.
import java.util.Scanner;
public class DemoE {
private static void Dfs(int index, int n, int[] boxs, int[] books) {
// index 表示当前盒子的下标
if (index == n + 1) { //如果当前所有的盒子已经被占用了, 表明前面的盒子已经放好牌了,可以打印每个盒子了
for (int i = 1; i <= n; i++) {
System.out.print(boxs[i] + " ");
}
System.out.println();
// 向上回退
return;
}
for (int i = 1; i <= n; i++) {
if (books[i] == 0) //第i号牌仍在手上
{
boxs[index] = i;
books[i] = 1; //现在第i号牌已经被用了
Dfs(index + 1, n, boxs, books); //处理下一个盒子
books[i] = 0; //从下一个盒子回退到当前盒子,取出当前盒子的牌,尝试放入其它牌
}
}
}
public static void main(String[] args) {
int n;
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
int[] boxs = new int[n + 1]; //盒子
int[] books = new int[n + 1]; //书
Dfs(1, n, boxs, books);
}
}
2 输入两个整数 n 和 m. 从数列1, 2, 3…n 中随意取几个数, 使其和等于 m, 要求将其中 所有的可能组合列出来.
public class DemoA {
static ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
static ArrayList<Integer> list = new ArrayList<>();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n, m;
while (sc.hasNext()) {
n = sc.nextInt();
m = sc.nextInt();
dfs(1, m, n);
for (ArrayList<Integer> l : res) {
int i = 0;
for (; i < l.size() - 1; i++) {
System.out.print(l.get(i) + " ");
}
System.out.println(l.get(i));
}
}
}
public static void dfs(int index, int count, int n) {
if (count == 0) {
res.add(new ArrayList<>(list));
} else {
for (int i = index; i <= count && i <= n; i++) {
list.add(i);
dfs(i + 1, count - i, n);
list.remove(list.size() - 1);
}
}
}
}
3 遍历二叉树路径问题. 输入一颗二叉树的根节点和一个整数, 打印出二叉树中 结点值的和为 输入整数的所有路径 (从根结点开始往下一直到叶结点).
回溯法. 本质是一个穷举的过程.
(1) 先添加值
(2) 在判定现有结果是否满足条件
(3) DFS
(4) 回溯
class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public class DemoA {
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
// result是 结果集
ArrayList<Integer> list = new ArrayList<>();
// list是 待选结果
FindPathDFS(root, target, result, list);
return result;
}
private void FindPathDFS(TreeNode root, int target, ArrayList<ArrayList<Integer>> result, ArrayList<Integer> list) {
if (root == null) {
return;
}
// 将当前值放入 list 待选结果集中
list.add(root.val);
target -= root.val;
// 待选结果是否符合条件, 是就添加到 结果集result 中.
if (root.left == null && root.right == null && target == 0) {
result.add(new ArrayList<>(list));
}
// DFS 深度优先
FindPathDFS(root.left, target, result, list);
FindPathDFS(root.right, target, result, list);
// 回溯. (检测 上一个节点 的右子树)
list.remove(list.size() - 1);
}
}
4 二叉树的所有路径组成的数之和.
public class DemoF {
public int sumNumbers(TreeNode root) {
int[] res = new int[1];
res[0] = 0;
if (root == null)
return 0;
dfs(root, res);
return res[0];
}
public int dfs(TreeNode root, int[] res) {
int k = 0;
if (root.left == null && root.right == null)
k = 1;
if (root.left != null)
k = k + 10 * dfs(root.left, res);
if (root.right != null)
k = k + 10 * dfs(root.right, res);
res[0] = res[0] + root.val * k;
return k;
}
}
5 全排列问题. 输入一个字符串, 按字典序打印出该字符串中字符的所有排列. 例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba.
思路: 想象为一棵 多叉树.
(1) 穷举所有的可能性.
(2) 剪枝 -> 根据特定的条件, 进而去掉不满足的.
(3) 排列这棵多叉树的路径.
public class DemoB {
public void Swap(char[] str, int i, int j) {
char temp = str[i];
str[i] = str[j];
str[j] = temp;
}
public boolean IsExist(ArrayList<String> result, char[] str) {
return result.contains(String.valueOf(str));
}
public void PermutationHelper(char[] str, int start, ArrayList<String> result) {
if (start == str.length - 1) {
if (!IsExist(result, str)) { // 剪枝
result.add(new String(str));
}
return;
}
for (int i = start; i < str.length; i++) { // start代表的是第一个元素, i和start交换, 意味着 i作为起始元素
Swap(str, start, i); // i对应的元素作为开始
PermutationHelper(str, start + 1, result); // 以i开头的 所有的可能性的元素,全部保存到result中
Swap(str, start, i); // 回溯
}
}
public ArrayList<String> Permutation(String str) {
ArrayList<String> result = new ArrayList<>();
if (str != null && str.length() > 0) {
PermutationHelper(str.toCharArray(), 0, result);
Collections.sort(result); // 题目要求是按 字典序 打印所有的字符串
}
return result;
}
}