2
Branch-and-Bound
作用
‧ 用來改良回溯演算法。
特點
‧ 使用狀態空間樹來解決問題。
‧ 不限制要怎麼走訪整棵樹。
( 回溯法有限制 )
‧ 只用來解決最佳化問題。
3
使用
Branch-and-Bound 修剪法之
最佳優先搜尋
‧ 除了使用 bound 值來決定一個節點是不是 promising 之外,
還必須加以比較每個子節點的 bound 值,然後挑選具有
最佳 bound 值的子節點進行走訪。
‧ 此方式通常會比事先決定的順序 ( 如 DFS) 更早到達代表
最佳解的節點。這也是 BFS 的簡易修正版。
4
5
BFS Algorithm
void breadth_first_tree_search ( tree T ) { queue_of_node Q ; // 宣告一個先進先出的佇列 node u , v ; initialize ( Q ) ; // 初始時將佇列清空 v = root of T ; // 走訪根節點 visit v ; enqueue( Q, v ) ; // 將根節點存入佇列尾端 while ( ! empty ( Q ) ) { // 當佇列中有節點時 dequeue ( Q, v ) ; // 從佇列前端取出一個節點 v
for ( each child u of v ) {
visit u ; // 走訪 v 的每個子節點 u
enqueue ( Q , u ) ; // 每走訪一個子節點 u 就將它
} // 放到佇列中
} }
6 1 2 3 4 3 4 5 6 7 4 5 6 7 8 9 5 6 7 8 9 10 11 12 6 7 8 9 10 11 12 7 8 9 10 11 12 13 14 8 9 10 11 12 13 14 12 13 14 15 16 13 14 15 16 取出 1 取出 2 取出 3 取出 4 取出 5 取出 6 取出 7 取出 8~11 取出 12 取出 13~16 放入 1
BFS 演算法走訪樹的範例
7
n=4 , W=16
i
P
iw
iP
i/w
i1
$40
2
$20
2
$30
5
$6
3
$50
10
$5
4
$10
5
$2
將物品依照單位重量獲益比 (P
i/ W
i) 值由大到小排序。
狀態空間樹的每個節點內部有三個數字,由上而下為:
全部獲益 ( 即 profit )
全部重量 ( 即 weight )
獲益上限值 ( 即 bound )
Branch-and-Bound 以 BFS 走訪法來解 0-1 背包
問題
8
nonpromising 策略
(1) 由樹的根節點往下走到第 i 層節點時,若已經沒空間
再放入更多物品的話,則該節點就是 nonpromising 。
設 weight = 在第 i 層節點所累積的物品總重量
若
weight W
則此節點一定是 nonpromising 。
背包載重限制9
nonpromising 策略 ( 續 )
(2) 從貪婪法則的觀點來找出一個較不明顯的判斷 promising
與否的方法。
1 1 k i j jw
weight
totweight
k k k i j jw
P
totweight
W
p
profit
bound
1 1 前 k-1 個物品的 總獲益 可以分配給 第 k 個物品 的容量 第 k 個物品 單位重量的 獲益 總重量 到第 i 層節點為止的總重量 第 i+1 層到 k-1 層節 點為止的總重量。 因為到第 k 層就爆了 。 第 i 層節點 的最大獲益 上限值。 第 1 到 i 層節點的總獲益值10
branch-and-bound 以 BFS 走訪法產生的樹
目前最佳值 maxprofit = 0
40
40
70
70
70
70
70
70
90
90
90
90
90
90
90
90
profit weight bound11
Breadth-first branch-and-bound Algorithm
void breadth_first_branch_and_bound ( state_space_tree T , number& best )
{ queue_of_node Q ; // 宣告一個佇列用來擺放欲走訪的節點 node u , v ; initialize ( Q ) ; // 啟始時將佇列清空 v = root of T ; enqueue ( Q , v ) ; // 走訪 root 節點,並將它加入佇列尾端 best = value ( v ) ; // 初始化目前最佳值 while ( ! empty ( Q ) ) { dequeue ( Q , v ) ; // 從佇列前端取出一個節點 v
for ( each child u of v ) { // 走訪 v 的每個子節點 u
if ( value ( u ) is better than best )
best = value ( u ) ; // 若找到更好的結果就更新目前最佳值
if ( bound( u ) is better than best )
enqueue( Q , u ) ; // 若一個節點是 promising 就把它加入佇列
} // 尾端,以待下一層走訪時使用
} }
12
演算法中的節點結構
struct node
{
int level ; // 該節點在樹中的層級
int profit ; // 目前累積的利益值
int weight ; // 目前累積的物品重量值
}
13
使用
Branch-and-bound 之廣度優先搜尋法來解決 0-1 背包問
題
問題:給定 n 個物件及其個別 weight 與 profit 值 ( 皆為正整數 ) 。
給定 W 值。在總重量不超過 W 的條件下,找出一組物件
使其總獲益是最大的。
輸入:正整數 n 和 W 。
陣列 w 與 p ( 索引均由 1 到 n ,且根據 p[i]/w[i] 由大到小
排序過 ) 。
輸出:正整數 maxprofit ( 即最大獲益 ) 。
本演算法只找出最大獲益值,若想要輸出被挑選的物品編號,可
以自行修改。
14
Algorithm ( 續 )
void knapsack2 ( int n , const int p[] , const int w[] , int W , int& maxprofit )
{ queue_of_node Q ; // 宣告一個要存放欲走訪節點的佇列
node u , v ;
initialize ( Q ) ; // 初始時將佇列清空
v.level = 0 ; v.profit = 0 ; v.weight = 0 ; // 將根節點初始化,並走訪它
maxprofit = 0 ; // 初始化目前最佳值
enqueue ( Q , v ) ; // 將根節點加入佇列尾端
while ( ! empty ( Q ) ) { // 只要佇列中還有節點未走訪,就一直做下去
dequeue ( Q , v ) ; // 從佇列前端取出欲走訪子節點的父節點
u.level = v.level + 1 ; // 將 u 設成 v 的下一層子節點其中之一
u.weight = v.weight + w[ u.level ] ; // 將 u 設成拿取下一層物品的子節點
u.profit = v.profit + p[ u.level ] ;
if ( u.weight <= W && u.profit > maxprofit ) // 若背包沒有爆掉,且獲
maxprofit = u.profit ; // 得利益值大於最佳值,則更新它
if ( bound ( u ) > maxprofit ) enqueue ( Q , u ) ; // 檢查 u 是否 promising
u.weight = v.weight ; u.profit = v.profit ; // 將 u 設成不拿取下一層物品
if ( bound ( u ) > maxprofit ) enqueue ( Q , u ) ; // 的子節點,並檢查它
} // 是否 promising
15
Algorithm ( 續 )
float bound ( node u ) // 回傳值就是 u 的 bound 值,呼叫者再把該值和目前最
{ index j , k ; // 佳值進行比較,以決定 u 是否為 promising
int totweight ; float result ;
if ( u.weight >= W ) return 0 ; // 背包爆掉則傳回 bound = 0
else {
result = u.profit ; // 先把 bound 值初設為到 u 節點為止的總共獲益值
j = u.level + 1 ;
totweight = u.weight ;
while ( j <= n && totweight + w[j] <= W ) {
totweight = totweight + w[j] ; // 盡可能多拿取一些物品
result = result + p[j] ; // 直到物品都拿光或是背包
j ++ ; // 爆掉為止
}
k = j ;
if ( k <= n ) result = result + (W-totweight)*(p[k]/w[k]) ; // 加上第 k 個
return result ; // 物品的部份利益
}
16
使用
Branch-and-bound 修剪法之
最佳
優先搜尋
下一頁
目前最佳值 maxprofit = 0
40
40
70
70
70
70
90
90
90
90
總共走訪了
11 個節點,
最佳值 =90
profit weight bound上一頁
17
Best-first Branch-and-bound Algorithm
void best_first_branch_and_bound ( state_space_tree T , number& best ) { priority_queue_of_node PQ ; // 使用優先權佇列來存放欲走訪的節點 node u , v ; initialize ( PQ ) ; v = root of T ; // 先走訪根節點,並初設目前最佳值 best best = value ( v ) ; insert ( PQ , v ) ; // 將根節點插入優先權佇列中 while ( ! empty ( PQ ) ) { // 只要優先權佇列中還有未走訪節點就繼續做 remove ( PQ , v ) ; // 取出佇列中優先權 ( 即 bound) 最高的節點
if ( bound ( v ) is better than best ) // 若它仍然還是 promising 則往
for ( each child u of v ) { // 下層走訪它的每個子節點
if ( value ( u ) is better than best )
best = value ( u ) ; // 更新最佳值
if ( bound ( u ) is better than best )
insert ( PQ , u ) ; // 若 u 為 promising ,則將
} // 它插入佇列中
} }
18
使用
Branch-and-bound 並以最佳優先搜尋法
來解決
0-1 背包問題
狀態空間樹中的節點結構
struct node {
int level ; // 節點在樹中的層級
int profit ; // 到目前為止的總獲利值
int weight ; // 到目前為止的總重量值
float bound ; // 節點的最大獲利能力值
}
19
Algorithm ( 續 )
void knapsack3 ( int n , const int p[] , const int w[] , int W , int& maxprofit ) { priority_queue_of_node PQ ;
node u , v ;
initialize ( PQ ) ;
v.level = 0 ; v.profit = 0 ; v.weight = 0 ; maxprofit = 0 ;
v.bound = bound ( v ) ; // 走訪根節點,並將它加入優先權佇列中
insert ( PQ , v ) ;
while ( ! empty ( PQ , v ) ) {
remove ( PQ , v ) ; // 將 bound 值最高的節點取出來
if ( v.bound > maxprofit ) { // 如果它仍然是 promising 就往下層展開
u.level = v.level + 1 ;
u.weight = v.weight + w[ u.level ] ;
u.profit = v.profit + p[ u.level ] ; // 先走訪左子節點
if ( u.weight <= W && u.profit > maxprofit )
20
Algorithm ( 續 )
u.bound = bound ( u ) ;
if ( u.bound > maxprofit ) // 若左子節點 promising ,則加
insert ( PQ , u ) ; // 入優先權佇列中
u.weight = v.weight ;
u.profit = v.profit ; // 走訪右子節點
u.bound = bound ( u ) ;
if ( u.bound > maxprofit ) // 若右子節點 promising ,則加
insert ( PQ , u ) ; // 入優先權佇列中
} }
}
21 0 40 40 70 70 70 70 90 90 90 90 (0,0 ) (1,1 ) (1,2) (2,1 ) (2,2) (1,2) (2,2 ) (1,2) (3,2) (3,3 ) (1,2) (3,2) (1,2 ) (3,2) (3,2 ) 115 115 82 115 98 82 98 82 80 98 82 80 82 80 80
優先權佇列變化圖
(1,2) : 82 < 90 nonpromising (3,2) : 80 < 90 nonpromising (0,0) (1,2) (2,1) (2,2) (3,3)22
售貨員旅行問題
0 4 17 7 18 2 0 9 7 11 16 7 0 5 4 7 8 7 0 14 20 10 4 14 0V
1V
2V
4V
V
33V
5 10 4 7 7 2此相鄰矩陣代表了
所有頂點間皆有邊線
相連的圖
左圖的最佳旅程
若城市數目很多,且是高度連結的,那麼使用回溯法會產
生太多旅行路線。因此改用 branch-and-bound 。
23
24
計算狀態樹中每個節點的
bound
值
在這個問題中求的 bound 值是由該節點繼續往下層節點走訪
的可能最小路徑長度。只有 bound 值比目前最佳值還小,這
個節點才是 promising 。
在任意旅程中,從每個頂點離開時,應採用其發出之最短邊
線:
V
1minimum ( 14, 4, 10, 20 ) = 4
V
2minimum ( 14, 7, 8, 7 ) = 7
V
3minimum ( 4, 5, 7, 16 ) = 4 => 4+7+4+2+4=21
V
4minimum ( 11, 7, 9, 2 ) = 2 一個旅程的長度下限
V
5minimum ( 18, 7, 17, 4 ) = 4 就是這些最小值的總
和,但不代表真有這
條路徑,可能不存在
25
計算狀態樹中每個節點的
bound 值
( 續 )
計算節點 [1,2] 的 bound 值:
V1 14 ( 已固定不能變動 )
V2 minimum ( 7 , 8 , 7 ) = 7
V3 minimum ( 4 , 7 , 16 ) = 4
V4 minimum ( 11 , 9 , 2 ) = 2
V5 minimum ( 18 , 7 , 17 , 4 ) = 4
0 4 17 7 18 2 0 9 7 11 16 7 0 5 4 7 8 7 0 14 20 10 4 14 0bound 值
= 14+7+4+2+4
= 31
26
計算節點 [1, 2, 3] 的 bound 值:
V
114 ( 已固定不能變動 )
V
27 ( 已固定不能變動 )
V
3minimum ( 7 , 16 ) = 7
V
4minimum ( 11 , 2 ) = 2
V
5minimum ( 18 , 4 ) = 4
0 4 17 7 18 2 0 9 7 11 16 7 0 5 4 7 8 7 0 14 20 10 4 14 0bound 值
= 14+7+7+2+4
= 34
計算狀態樹中每個節點的
bound 值
( 續 )
27
狀態樹與
Bound 值
最佳解 = 無限大 37 31 31 31 31 31 31 30 30 5, 428
Algorithm 使用 Branch-and-bound 及最佳搜尋
法來解決售貨員旅行問題
狀態樹中的節點結構
struct node {
int level ;
// 節點在樹中的層級
ordered set path ;
// 到該節點為止的旅程路徑
number bound ;
// 該節點的旅程下限值
29
Algorithm ( 續 )
問題:在一有向權重圖中找出一條最佳旅程。 weight 必須為
非負整數。
輸入:一個有向權重圖 ( 以二維陣列 W 來表示,行列索引均
由 1 到 n , W[i][j] 代表由第 i 個頂點連到第 j 個頂點之
weight ) 。
該圖中的頂點數, n 。
輸出:變數 minlength ,其值為一條最佳旅程的長度。
變數 opttour ,其值為一條最佳旅程。
30
Algorithm ( 續 )
void travel2 ( int n , const number W[][] , ordered_set& opttour , number& minlength ) { priority_queue_of_node PQ ; node u , v ; initialize ( PQ ) ; v.level = 0 ; v.path = [1] ; // 令頂點 1 為起點 ( 即根節點 )
v.bound = bound( v ) ; // 計算根節點的 bound 值
minlength = 無限大 ; // 初始化最佳值
insert ( PQ , v ) ;
while ( ! empty ( PQ ) ) {
remove ( PQ , v ) ; // 取出下一個要展開的節點
if ( v.bound < minlength ) { // 如果它仍然是 promising
u.level = v.level + 1 ; // 開始計算它所有下一層節點
for ( all i such that 2 <= i <= n && i is not in v.path ) { u.path = v.path ;
31
Algorithm ( 續 )
if ( u.level == n - 2 ) { // 如果 u 在倒數兩層就直接處理
put index of only vertex not in u.path
at the end of u.path ; // 將最後一頂點加入路徑
put 1 at the end of u.path ; // 把頂點 1 加入
if ( length ( u ) < minlength ) { minlength = length ( u ) ; // 更新最佳值 opttour = u.path ; // 更新最佳路徑 } } else { // 還不到旅程的終點
u.bound = bound ( u ) ; // 計算 bound 值
if ( u.bound < minlength ) // 若 promising
insert ( PQ , u ) ; // 放入優先權佇列中
} }
} } }