子集求和问题属于难以高效解决的计算问题中的一类,用于以信息保密为目标的应用中。
子集求和问题的定义为:给定一个整数集和目标值,确定是否可以找到这些整数的一个子集,使其总和等于指定的目标值。
比如给定集合{-2,1,3,8}和目标值7,那么问题的答案就是是,因为有子集{-2,1,8}加起来对于7。但是如果目标值是5,答案就为否。
所以我们很容易定义出递归函数原型:
boolean subsetSumExists(TreeSet<int> set, int target);
分析简单情况——集合为空。除非目标值为0,递归结束,否则我们无法用空集中的元素产生目标值。
if (set.isEmpty()) {
return target == 0;
} else {...//让问题规模变小的方案}
我们要让集合变小,朝着集合为空的目标迈进。就需要从集合中不断导出元素。导出的元素无非两种情况,1、在构成目标值的子集里,此时就要将target减去这个导出元素;2、不在子集里,就不用减,递归调用还是传target。要么包含要么排除,即包含/排除模式。
分析了这些,递归代码就好写了:
import java.util.TreeSet;
public class SubsetSum {
public void run() {
TreeSet<Integer> set = createIntSet(-2, 1, 3, 8);
System.out.println("set = " + toString(set));
int min = minSum(set);
int max = maxSum(set);
for (int i = min; i <= max; i++) {
System.out.println("subsetSumExists(set, " + i + ") = " +
subsetSumExists(set, i));
}
}
private TreeSet<Integer> createIntSet(int... args) {
TreeSet<Integer> set = new TreeSet<Integer>();
for (int n : args) {
set.add(n);
}
return set;
}
private boolean subsetSumExists(TreeSet<Integer> set, int target) {
if (set.isEmpty()) {
return target == 0;
} else {
int element = set.first();
TreeSet<Integer> rest = new TreeSet<Integer>(set);
rest.remove(element);
return subsetSumExists(rest, target)
|| subsetSumExists(rest, target - element);
}
}
private int minSum(TreeSet<Integer> set) {
int total = 0;
for (int element : set) {
if (element < 0) total += element;
}
return total;
}
private int maxSum(TreeSet<Integer> set) {
int total = 0;
for (int element : set) {
if (element > 0) total += element;
}
return total;
}
/**
* Returns the String representation of the set using the
* conventional curly-brace representation.
*/
private String toString(TreeSet<Integer> set) {
String str = "{";
for (int element : set) {
if (str.length() > 1) str += ",";
str += " " + element;
}
return str + " }";
}
/* Main program */
public static void main(String[] args) {
new SubsetSum().run();
}
}
参考:《Programming Abstractions in Java》