1、介绍一下子集树,什么是子集树?
当所给问题是从n个元素的集合S中找出满足某些条件或者性质的子集时,解空间是子集树;
比如典型的0-1背包问题-----and-----轮船装载问题:
解空间 就是指满足问题要求的所有解组成的集合,一个问题的解往往包含了得到这个解的每一步,就是抽象的对应解空间树的从根节点到叶子节点的一条路径。时间复杂度为O(2的n次方)。
下面现用我们老师给出的一个引入话题的例子,一个递归打印,理解一下打印出多少个hello world?
@Test
public void test01(){
int[] arr = {1,2,3};
backstrace01(arr, 0, arr.length);
}
private void backstrace01(int[] arr, int i, int length) {
if(i == length){
System.out.println("hello world!");
} else {
backstrace01(arr, i+1, length);
backstrace01(arr, i+1, length);
}
}
呐,这是结果,打印了8个hello world如图所示:
运行上面的代码可以看出来,打印出序列{1,2,3}的所有子集情况,也就是从根节点到叶子节点,每一条路径都是一个解,所以打印了8次hello world :
例如求一组整数序列,选择其中一部分整数,让选择的整数和与剩下的整数和的差最小:
public class ChildTree {
static int[] arr = {12,52,60,13,32,28,38};
static int[] x = new int[arr.length];
static int[] bestx = new int[arr.length];
static int r = 0;//没有选择的整数的和
static int min = Integer.MAX_VALUE;
public static void main(String[] args) {
for (int i = 0; i < arr.length; i++) {
r += arr[i];
}
backstrace(arr, 0);
System.out.println("min:" + min);
System.out.println(Arrays.toString(bestx));
}
private static void backstrace(int[] arr, int i) {
if(i == arr.length){
int sum = 0;
for (int j = 0; j < arr.length; j++) {
if(x[j] == 1){
sum += arr[j];
}
}
// sum
int ret = Math.abs(sum - r);
if(ret < min){
min = ret;
for (int j = 0; j < x.length; j++) {
bestx[j] = x[j];
}
}
} else {
r -= arr[i];
x[i] = 1;
backstrace(arr, i+1);
r += arr[i];
x[i] = 0;
backstrace(arr, i+1);
}
}
}
2、例1:0-1背包问题
问题描述:有一组物品,重量分别为:w1 w2 …wn, 价值分别:V1 V2 … Vn, 现要求背包承重为C, 问怎么装包才能使不超重的情况下价值达到最大?
(看到问题的第一瞬间想到的使运筹学的最优化问题,用动态规划来解决,在此用子集树解决)
因为这个问题的解是给出的原物品的一个子集,可用子集树来解决,然后添加剪枝操作,提高算法效率,这样也可以高效解决此问题:
public class ChildTreePackage {
static int[] w = {5,8,7,9,6};
static int[] v = {12,9,13,10,11};
static int[] x = new int[w.length];
static int[] bestx = new int[w.length];
static int cw = 0;
static int cv = 0;//已经选择物品的价值
static int bestv = Integer.MIN_VALUE;
static int c = 18;
static int r = 0;
public static void main(String[] args) {
for (int i = 0; i < v.length; i++) {
r += v[i];
}
backstrace(0);
System.out.println("bestv:" + bestv);
System.out.println(Arrays.toString(bestx));
}
private static void backstrace(int i){
if(i == w.length){
if(bestv < cv){
bestv = cv;//更新最优值
for(int j = 0;j < x.length;j++){//更新最优值选择的子集
bestx[j] = x[j];
}
}
}else{
r -= v[i];
if(cw + w[i] <= c) {//左剪枝
cw += w[i];
cv += v[i];
x[i] = 1;//向左走
backstrace(i + 1);
cw -= w[i];
cv -= v[i];
![}](https://img-blog.csdnimg.cn/20190725230616402.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE4Nzk2Mw==,size_16,color_FFFFFF,t_70)
//右剪枝
if(cv + r > bestv) {//当前已选择物品的总价值+除右孩子以外的左孩子的值 > 当前最好的价值
x[i] = 0;//向右走
backstrace(i + 1);//i节点的右孩子
}
r += v[i];//不放在前面加回来是因为不需要加右孩子等不需要选取的值
}
}
}
结果中可以得到,最好的价值是36,然后下面的0表示不选,1表示加入背包:
3、例2:轮船装载问题
问题描述:有一组物品,其重量分别是:w1,w2…wn
现在有两艘轮船,其容量分别是C1和C2,满足w1+w2+…+wn <= C1 + C2
问怎么装载物品,才能够把物品全部装上轮船?
其实这个问题与0-1背包问题异曲同工,同样子集树解决:
public class 轮船装载问题 {
static int[] w = {12,18,21,14,9,10};
static int c1 = 55;
static int c2 = 30;
static int[] x = new int[w.length];
static int[] bestx = new int[w.length];
static int bestw = Integer.MIN_VALUE;
static int cw = 0;
static int r = 0; // 记录物品i后面所有物品的总重量
public static void main(String[] args) {
for (int i = 0; i < w.length; i++) {
r += w[i];
}
backstrace(0);
int sum = 0;
for (int i = 0; i < bestx.length; i++) {
if(bestx[i] == 0){
sum += w[i];
}
}
if(sum > c2){
System.out.println("物品无法装载到轮船!");
return;
}
System.out.println("C1:" + c1 + "装载物品:");
for (int i = 0; i < bestx.length; i++) {
if(bestx[i] == 1){
System.out.print(w[i] + " ");
}
}
System.out.println();
System.out.println("C2:" + c2 + "装载物品:");
for (int i = 0; i < bestx.length; i++) {
if(bestx[i] == 0){
System.out.print(w[i] + " ");
}
}
System.out.println();
}
private static void backstrace(int i) {
if(i == w.length){
if(cw > bestw){
bestw = cw;
for (int j = 0; j < x.length; j++) {
bestx[j] = x[j];
}
}
} else {
r -= w[i];
if(cw + w[i] <= c1){
cw += w[i];
x[i] = 1;
backstrace(i+1); // 选择第i个节点
cw -= w[i];
}
if(cw + r > bestw){
x[i] = 0; // 才有必要去往右边的i节点
backstrace(i+1); // 没选择第i个节点
}
r += w[i];
}
}
}
结果如下: