1、分支限界法介紹
分支限界法類似於回溯法,也是在問題的解空間上搜索問題解的算法。一般情況下,分支限界法與回溯法的求解目標不同。回溯法的求解目標是找出解空間中滿足約束條件的所有解;而分支限界法的求解目標則是找出滿足約束條件的一個解,或是在滿足約束條件的解中找出使某一目標函數值達到極大或極小的解,即在某種意義下的最優解。
由於求解目標不同,導致分支限界法與回溯法對解空間的搜索方式也不相同。回溯法以深度優先的方式搜索解空間,而分支限界法則以廣度優先或以最小耗費優先的方式搜索解空間。
分支限界法的搜索策略是,在擴展結點處,先生成其所有的兒子結點(分支),然后再從當前的活結點表中選擇下一擴展結點。為了有效地選擇下一擴展結點,加速搜索的進程,在每一個活結點處,計算一個函數值(限界),並根據函數值,從當前活結點表中選擇一個最有利的結點作為擴展結點,使搜索朝着解空間上有最優解的分支推進,以便盡快地找出一個最優解。這種方式稱為分支限界法。人們已經用分支限界法解決了大量離散最優化的問題。
2、常見的兩種分支限界法
- 隊列式(FIFO)分支限界法:按照先進先出原則選取下一個節點為擴展節點。 活結點表是先進先出隊列。
LIFO分支限界法:活結點表是堆棧。
- LC(least cost)分支限界法(優先隊列式分支限界法):按照優先隊列中規定的優先級選取優先級最高的節點成為當前擴展節點。 活結點表是優先權隊列,LC分支限界法將選取具有最高優先級的活結點出隊列,成為新的E-結點。
FIFO分支限界法搜索策略:
§一開始,根結點是唯一的活結點,根結點入隊。
§從活結點隊中取出根結點后,作為當前擴展結點。
§對當前擴展結點,先從左到右地產生它的所有兒子,用約束條件檢查,把所有滿足約束函數的兒子加入活結點隊列中。
§再從活結點表中取出隊首結點(隊中最先進來的結點)為當前擴展結點,……,直到找到一個解或活結點隊列為空為止。
優先隊列式分支限界法搜索策略:
§對每一活結點計算一個優先級(某些信息的函數值);
§根據這些優先級從當前活結點表中優先選擇一個優先級最高(最有利)的結點作為擴展結點,使搜索朝着解空間樹上有最優解的分支推進,以便盡快地找出一個最優解。
§再從活結點表中下一個優先級別最高的結點為當前擴展結點,……,直到找到一個解或活結點隊列為空為止。
3、解決01背包問題算法的思想
01背包問題狀態空間樹
- FIFO限界:若當前分支的“裝載的價值上界”,比現有的最大裝載的價值小,則該分支就無需繼續搜索。
隊列式分支法
隊列式分支限界法
-
優先隊列限界:優先隊列搜索得到當前最優解作為一個“界”,對上界(或下界)不可能達到(大於)這個界的分支則不去進行搜索,這樣就縮小搜索范圍,提高了搜索效率。
優先隊列式分支法
優先隊列式分支限界法
算法首先檢查當前擴展結點的左兒子結點的可行性。如果該左兒子結點是可行結點,則將它加入到子集樹和活結點優先隊列中。當前擴展結點的右兒子結點一定是可行結點,僅當右兒子結點滿足上界約束時才將它加入子集樹和活結點優先隊列。當擴展到葉節點時為問題的最優值。
上界函數
1: // maxBound函數求最大上界
2: private double maxBound(int t) {
3: double left = max - c_weight, b = c_value;
4: // 剩余容量和價值上界
5: while (t < n && weight[t] <= left)
6: // 以物品單位重量價值遞減裝填剩余容量
7: {
8: left -= weight[t];
9: b += value[t];
10: t++;
11: }
12: if (t < n)
13: b += value[t] / weight[t] * left;
14: // 裝填剩余容量裝滿背包
15: return b;
16: }
分支限界算法
1: int i = 0;
2: double upper = maxBound(i);
3: // 調用MaxBound求出價值上界,best為最優值
4: while (true)
5: // 非葉子結點
6: {
7: double wt = c_weight + weight[i];
8: if (wt <= max)
9: // 左兒子結點為可行結點
10: {
11: if (c_value + value[i] > bestv)
12: bestv = c_value + value[i];
13: addLiveNode(upper, c_value + value[i], c_weight + weight[i],
14: i + 1);
15: }
16: upper = maxBound(i + 1);
17: if (upper >= bestv) // 右子數可能含最優解
18: addLiveNode(upper, c_value, c_weight, i + 1);
19: if (heap.empty())
20: return bestv;
21: HeapNode node = heap.peek();
22: // 取下一擴展結點
23: heap.pop();
24: c_weight = node.weight;
25: c_value = node.value;
26: upper = node.upper;
27: i = node.level;
28: }
4、算法實現
1: import java.util.Stack;
2:
3: class HeapNode {
4: double upbound; // 結點的價值上界
5: double value; // 結點所對應的價值
6: double weight; // 結點所相應的重量
7:
8: int level; // 活節點在子集樹中所處的層序號
9:
10: public HeapNode() {
11: }
12: }
13:
14: // 分支限界法實現01背包問題
15: public class BB_Knapsack01 {
16:
17: int[] weight;
18: int[] value;
19: int max; // 背包的最大承重量
20:
21: int n;
22:
23: double c_weight; // 當前背包重量
24: double c_value; // 當前背包價值
25:
26: double bestv; // 最優的背包價值
27:
28: Stack<HeapNode> heap;
29:
30: public BB_Knapsack01() {
31: weight = new int[] { 15, 16, 15, 0 };
32: value = new int[] { 25, 45, 25, 0 };
33: max = 30;
34:
35: n = weight.length - 1;
36:
37: c_weight = 0;
38: c_value = 0;
39: bestv = 0;
40:
41: heap = new Stack<HeapNode>();
42: }
43:
44: // 求子樹的最大上界
45: private double maxBound(int t) {
46: double left = max - c_weight;
47: double bound = c_value;
48: // 剩余容量和價值上界
49: while (t < n && weight[t] <= left) {
50: left -= weight[t];
51: bound += value[t];
52: t++;
53: }
54: if (t < n)
55: bound += (value[t] / weight[t]) * left; // 裝填剩余容量裝滿背包
56: return bound;
57: }
58:
59: // 將一個新的活結點插入到子集樹和最大堆heap中
60: private void addLiveNode(double upper, double cvalue, double cweight,
61: int level) {
62: HeapNode node = new HeapNode();
63: node.upbound = upper;
64: node.value = cvalue;
65: node.weight = cweight;
66: node.level = level;
67: if (level <= n)
68: heap.push(node);
69: }
70:
71: // 利用分支限界法,返回最大價值bestv
72: private double knapsack() {
73: int i = 0;
74: double upbound = maxBound(i);
75: // 調用maxBound求出價值上界,bestv為最優值
76: while (true) // 非葉子結點
77: {
78: double wt = c_weight + weight[i];
79: if (wt <= max)// 左兒子結點為可行結點
80: {
81: if (c_value + value[i] > bestv)
82: bestv = c_value + value[i];
83: addLiveNode(upbound, c_value + value[i], c_weight + weight[i],
84: i + 1);
85: }
86: upbound = maxBound(i + 1);
87: if (upbound >= bestv) // 右子樹可能含最優解
88: addLiveNode(upbound, c_value, c_weight, i + 1);
89: if (heap.empty())
90: return bestv;
91: HeapNode node = heap.peek();
92: // 取下一擴展結點
93: heap.pop();
94: //System.out.println(node.value + " ");
95: c_weight = node.weight;
96: c_value = node.value;
97: upbound = node.upbound;
98: i = node.level;
99: }
100: }
101:
102: public static void main(String[] args) {
103: // TODO Auto-generated method stub
104: BB_Knapsack01 knap = new BB_Knapsack01();
105: double opt_value = knap.knapsack();
106: System.out.println(opt_value);
107: }
108: }