回溯法

回溯法是一种选优搜索法,通过深度优先搜索解空间树,用于解决各种组合问题。本文介绍了回溯法的基本原理,包括如何判断回溯点,并给出了典型的应用示例,如扑克牌放入盒子、求和组合、二叉树路径和全排列问题。回溯法本质上是一个穷举过程,通过添加值、判定条件、深度优先搜索和回溯来寻找解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

回溯法(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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值