分支界限法
简单概述
分支界限法类似与回溯法,也是在问题解空间中搜索问题解的一种算法。
分支界限法与回溯法对比:
1. 求解目标不同:回溯法可以用于求解目标是找出解空间树中满足约束条件的所有解,而分支界限法求解的目标通常是找出一个满足约束条件的解,或者最优解。
2. 搜索方式不同:回溯法主要以深度优先的方式搜索解空间树,而分支界限法则主要以广度优先或者函数优先的方式搜索解空间树。
基本思想
在分支界限法中,每个活结点只有一次机会成为扩展节点,一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或者导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活节点列表为空时为止。
代码框架:
1 2 3 4 5 6 7 8 9
| Q = {q0}; void Branch&Bound () { while (Q!=) { select a node q from Q; Branch(q, Q1); add (Q1, Q); } }
|
常见的两种分支搜索法
队列式(FIFO)搜索法:按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
优先队列式搜索法:按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
最大堆(最大优先队列):最大效益优先
最小堆(最小优先队列):最小耗费优先
经典问题案例
01背包问题
问题描述
有n 种不同的物品,每个物品有两个属性,size 体积,value 价值,现在给一个容量为 w 的背包,问最多可带走多少价值的物品。
例:编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,每件物品数量只有一个,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
解题思路
问题的关键在于解空间树的界的设计,好的界限函数可以大大提高算法的效率。01背包问题是求解在限定条件下的极大值,所以上界函数的设计很重要,因为我们可以根据计算出来的上界值和之前算出的下界做比较,如果当前的上界比下界还要小,那么就可以剪枝,减少计算量。
01背包问题的上界函数可以使用这种方法,我们先删除比背包剩余容量还要大的物品(因为不可能放得下),然后按照贪心算法的方式:分别计算出每个物品的价值比(价值/重量),价值比大的就先装。因为是计算上界,我们不用考虑装不下的问题,如果装不下,就拆分,按照比例装入背包。下界函数的设计和上界函数的思路差不多,但是由于是下界,所以我们要考虑装不下的情况,如果装不下,那就不装,换成下一个价值比大的物品继续装。
构造解空间树的过程如下:
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
| package branch;
import java.util.*;
public class Package01 {
private int maxValue = Integer.MIN_VALUE; private static LinkedList<Node> heap = new LinkedList<Node>();
public int getMaxValue() { return maxValue; }
public void setMaxValue(int maxValue) { this.maxValue = maxValue; }
public static class Node implements Comparable{ double ub; int lb; int level; Node parent; int cValue; int restPWeight; public Node(double ub,int lb,int level,Node node,int cValue,int restPWeight){ this.ub = ub; this.lb = lb; this.level = level; this.parent = node; this.cValue = cValue; this.restPWeight = restPWeight; }
@Override public int compareTo(Object o) { double compareUb = ((Node)o).ub; if(ub < compareUb) return 1; if(ub == compareUb) return 0; return -1; } public boolean equals(Object x){ return ub==((Node)x).ub; } }
private static int computeLb(int[] weight, int[] value, int pWeight){ int lb = 0; Map<Integer,Integer> omap = new HashMap<>(); Map<Integer,Integer> map = new TreeMap<>(new Comparator<Integer>() { public int compare(Integer obj1, Integer obj2) { return obj2.compareTo(obj1); } }); for(int i = 1;i <= weight.length;i++){ omap.put((value[i-1]/weight[i-1]),i); } map.putAll(omap); for(Map.Entry<Integer,Integer> entry: map.entrySet()){ int number = entry.getValue(); if(weight[number - 1] > pWeight) continue; pWeight = pWeight - weight[number - 1]; lb = lb + value[number - 1]; } return lb; }
private static double computeUb(int[] weight,int[] value,int pWeight){ double ub = 0; int cpweight = pWeight; Map<Integer,Integer> omap = new HashMap<>(); Map<Integer,Integer> map = new TreeMap<>(new Comparator<Integer>() { public int compare(Integer obj1, Integer obj2) { return obj2.compareTo(obj1); } }); for(int i = 1;i <= weight.length;i++){ omap.put((value[i-1]/weight[i-1]),i); } map.putAll(omap);
for(Map.Entry<Integer,Integer> entry: map.entrySet()){ int number = entry.getValue(); if(weight[number - 1] > pWeight)continue; if(weight[number - 1] > cpweight){ ub = ub + cpweight * entry.getKey(); return ub; }else { cpweight = cpweight - weight[number - 1]; ub = ub + value[number - 1]; } } return ub; }
public static void main(String[] args) { int[] weight = {4,7,5,3,2,1}; int[] value = {40,42,25,12,6,2}; int maxLevel = weight.length; int pWeight = 10; int[] result = new int[maxLevel]; int maxValue = 0; int lb = computeLb(weight,value,pWeight); double ub = computeUb(weight,value,pWeight); System.out.println(lb+" "+ub); int level = 1; Node node = new Node(ub,lb,level,null,0,pWeight); while (node!=null&&node.level<=maxLevel){ if(node.restPWeight-weight[node.level - 1] >= 0) { int[] cweight = Arrays.copyOfRange(weight, node.level, maxLevel); int[] cvalue = Arrays.copyOfRange(value, node.level, maxLevel); double nodeub = node.cValue + value[node.level - 1] + computeUb(cweight, cvalue, node.restPWeight - weight[node.level - 1]); int nodelb = lb; int nodevalue = value[node.level - 1]; if (nodeub >= lb) { Node childNode = new Node(nodeub, nodelb, node.level + 1, node, node.cValue + nodevalue, node.restPWeight - weight[node.level - 1]); heap.add(childNode); int cValue = node.cValue+value[node.level-1]; if(cValue > maxValue) maxValue = cValue; result[node.level - 1] = 1; Collections.sort(heap); } } int[] cweight1 = Arrays.copyOfRange(weight,node.level,maxLevel); int[] cvalue1 = Arrays.copyOfRange(value,node.level,maxLevel); double nodeub1 = node.cValue+computeUb(cweight1,cvalue1,node.restPWeight); int nodelb1 = lb; if(nodeub1>=lb){ Node childNode1 = new Node(nodeub1,nodelb1,node.level+1,node,node.cValue,node.restPWeight); heap.add(childNode1); int cValue = node.cValue; if(cValue > maxValue) maxValue = cValue; Collections.sort(heap); } node = heap.poll(); } System.out.println(maxValue); for(int i = 0;i<result.length;i++){ System.out.print(result[i]+" "); } } }
|
最大团问题
问题概述
举个例子:下图G中,子集{1,2}是G的大小为2的完全子图。这个完全子图不是团,因为它被G更大的完全子图{1,2,5}包含。{1,2,5}是G的最大团。{1,4,5}和{2,3,5}也是G的最大团。
解题思路
我们用分支界限法解决问题时,首先要构建问题的解空间数,从题意不难看出,问题是一个子集树,对于每个顶点,都有选或不选两种情况,所以是子集树,然后是上界函数和下界函数。这个问题的下界函数不容易求解,我们可以直接把当前最优解当作下界函数。
上界函数:用变量cliqueSize表示与该结点相应的团的顶点数;level表示结点在子集空间树中所处的层次;用cliqueSize+n-level+1作为顶点数上界upperSize的值。
在此优先队列式分支限界法中,upperSize实际上也是优先队列中元素的优先级。算法总是从活结点优先队列中抽取具有最大upperSize值的元素作为下一个扩展元素。
算法思想:
子集树的根结点是初始扩展结点,对于这个特殊的扩展结点,其cliqueSize的值为0。 算法在扩展内部结点时,首先考察其左儿子结点。在左儿子结点处,将顶点i加入到当前团中,并检查该顶点与当前团中其他顶点之间是否有边相连。当顶点i与当前团中所有顶点之间都有边相连,则相应的左儿子结点是可行结点,将它加入到子集树中并插入活结点优先队列,并判断是否可以更新最优解,否则就不是可行结点。
接着继续考察当前扩展结点的右儿子结点。当upperSize > bestn时,右子树中可能含有最优解,此时将右儿子结点加入到子集树中并插入到活结点优先队列中。不断从优先队列中选取活节点,并按照上面的方式(先左子树,后右子树)扩展节点,直到满足终止条件。
终止条件:
算法的while循环的终止条件是遇到子集树中的一个叶结点(即n+1层结点)成为当前扩展结点。
对于子集树中的叶结点,有upperSize=cliqueSize。此时活结点优先队列中剩余结点的upperSize值均不超过当前扩展结点的upperSize值,从而进一步搜索不可能得到更大的团,此时算法已找到一个最优解。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| package branch;
import java.util.Collections; import java.util.LinkedList; import java.util.Vector;
import static java.lang.Integer.max;
public class MaxClique {
private LinkedList<Node> heap = new LinkedList<Node>();
public class Node implements Comparable{ double cliqueSize; int level; Vector<Integer> selectedNode = new Vector<>(); public Node(double cliqueSize,int level,Vector<Integer> nodes){ this.selectedNode.addAll(nodes); this.cliqueSize = cliqueSize; this.level = level; }
@Override public int compareTo(Object o) { double compareUb = ((Node)o).cliqueSize; if(cliqueSize< compareUb) return 1; if(cliqueSize == compareUb) return 0; return -1; } public boolean equals(Object x){ return cliqueSize==((Node)x).cliqueSize; } }
public int maxClique(int[][] nodeIndex,int n){ int bestNum = 0; int cliqueSize = 0; int level = 0; int upperSize = cliqueSize + n - level; Vector<Integer> selectedNode = new Vector<>();
Node node = new Node(cliqueSize,level,selectedNode); while(node.level < n - 1){ if(node.level + 1 == n - 1) return node.selectedNode.size(); if(ifEdge(nodeIndex,node.level + 1,node.selectedNode)){ Vector<Integer> childSelectedNode = new Vector<>(); childSelectedNode.addAll(node.selectedNode); childSelectedNode.add(level + 1); int upper = node.selectedNode.size() + n - node.level; Node leftChild = new Node(upper,node.level + 1,childSelectedNode); heap.add(leftChild); Collections.sort(heap); } if(node.selectedNode.size() + n - node.level >= bestNum){ Vector<Integer> childSelectedNode = new Vector<>(); childSelectedNode.addAll(node.selectedNode); int upper = node.selectedNode.size() + n - node.level - 1; Node rightChild = new Node(upper,node.level + 1, childSelectedNode); heap.add(rightChild); Collections.sort(heap); } node = heap.poll(); bestNum = max(bestNum,node.selectedNode.size()); } return 0; }
public boolean ifEdge(int[][] nodeIndex,int level,Vector<Integer> selectedNode){ for(int i = 0;i < selectedNode.size();i++){ if(nodeIndex[selectedNode.get(i)][level] == 0) return false; } return true; } }
|
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class TestMaxClique { public static void main(String[] args) { int[][] nodes = {{0,0,0,0,0,0}, {0,0,1,0,1,1}, {0,1,0,1,0,1}, {0,0,1,0,0,1}, {0,1,0,0,0,1}, {0,1,1,1,1,0}}; int n = nodes.length; MaxClique maxClique = new MaxClique(); int num = maxClique.maxClique(nodes,n); System.out.println("最大团的顶点数为:" + num); } }
|
测试结果: