回溯算法 - 子集和问题

(1)问题描述:子集和问题的一个实例为<data, num>。其中 data = {x1, x2, ......, xn} 是一个正整数的集合,targetValue 是一个正整数。子集和问题判定是否存在 data 的一个子集 data1,使得

x1 + x2 + ...... + xn = targetValue (x € data1)

(2)算法设计:使用回溯算法子集树来解决,对于给定的集合 data = {x1, x2, ......, xn} 和正整数 targetValue,计算 data 的一个子集 data1,满足【x1 + x2 + ...... + xn = targetValue (x € data1)】

(3)算法代码:

public class SubsetSum {

    /**
     * 目标值
     */
    private static Integer targetValue;

    /**
     * 当前所选元素之和
     */
    private static Integer sum = 0;

    /**
     * 数据个数
     */
    private static Integer num;

    /**
     * 未确定值
     */
    private static Integer indeterminacyValue = 0;

    /**
     * 数据数组
     */
    private static Integer[] data;

    /**
     * 数据存放【0:不存放 1:存放】
     */
    private static Integer[] store;

    /**
     * 初始化数据
     */
    private static void initData() {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入目标值:");
        targetValue = input.nextInt();
        
        System.out.println("请输入数据个数:");
        num = input.nextInt();

        data = new Integer[num];
        store = new Integer[num];
        System.out.println("请输入各个数:");
        for (int i = 0; i < data.length; i++) {
            data[i] = input.nextInt();
            store[i] = 0;               // 初始化都不存放
            indeterminacyValue += data[i];
        }
    }

    /**
     * 回溯查找
     */
    private static Boolean backtrack(int i) {
        if (sum == targetValue) {   // 找到可行解,直接返回 true
            return true;
        }
        if (i == data.length) {     // 找不到可行解,直接返回 false
            return false;
        }
        indeterminacyValue -= data[i];                  // 计算还未确定数的总和
        if (sum + data[i] <= targetValue) {             // 当前 sum + data[i] <= targetValue 直接进入左子树
            store[i] = 1;                               // 数据 i 存放,列入所选加数之中
            sum += data[i];
            if (backtrack(i + 1)) {                  // 继续深入下一层判定求和
                return true;
            }
            sum -= data[i];                             // 求解深入完毕,若不满足所求的解,需要回溯,恢复当前的 sum 起始值
        }
        if (sum + indeterminacyValue >= targetValue) {  // 剪枝函数【若当前 sum + 未确定的值 >= 目标值,才进入右子树深度搜索;否则没有任何意义】
            store[i] = 0;                               // 数据 i 此时不存放,列入所选加数之中
            if (backtrack(i + 1)) {
                return true;
            }
        }
        indeterminacyValue += data[i];                  // 求解深入完毕,若不满足所求的解,需要回溯,恢复当前的 indeterminacyValue 起始值
        return false;
    }

    /**
     * 输出
     */
    private static void print() {
        System.out.println("\n数据数组:");
        Stream.of(data).forEach(element -> System.out.print(element + " "));
        System.out.println();
        System.out.println("数据存放:");
        Stream.of(store).forEach(element -> System.out.print(element + " "));
        System.out.println();
        System.out.println("组成该目标值的数为:");
        for (int i = 0; i < store.length; i++) {
            if (store[i] == 1) {
                System.out.print(data[i] + " ");
            }
        }
        System.out.println();
    }
    
    public static void main(String[] args) {
        // 初始化数据
        initData();

        // 回溯查找
        backtrack(0);

        // 输出
        print();
    }

}
子集和问题核心代码

(4)输入输出:

请输入目标值:
10
请输入数据个数:
5
请输入各个数:
2 2 6 5 4

数据数组:
2 2 6 5 4 
数据存放:
1 1 1 0 0 
组成该目标值的数为:
2 2 6 
输入输出

(5)总结:子集和同样也完全体现了回溯法中子集树的核心思想,时间复杂度 O(2n) ,通过存与不存,判断是否剪枝,进入深度搜索一个解,一旦搜索到一个解直接返回即可;

  建议:若肉眼看不太懂,可以在纸上根据我的代码思路,画一画走一遍求解的流程,便于理解代码,掌握回溯法子集树的核心思想;

posted @ 2020-02-13 10:39  菜鸟的奋斗之路  阅读(2757)  评论(0编辑  收藏  举报