(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) ,通过存与不存,判断是否剪枝,进入深度搜索一个解,一旦搜索到一个解直接返回即可;
建议:若肉眼看不太懂,可以在纸上根据我的代码思路,画一画走一遍求解的流程,便于理解代码,掌握回溯法子集树的核心思想;
来源:https://www.cnblogs.com/blogtech/p/12302565.html
