55
回回回回回 (Backtracking)

回溯演算法 (Backtracking)

  • Upload
    aadi

  • View
    57

  • Download
    0

Embed Size (px)

DESCRIPTION

回溯演算法 (Backtracking). The Divide-and-Conquer Strategy ( 個各擊破 ) binary Searching 、 Quick Sort…. The Greedy Method( 貪婪演算法 ) Prim MST 、 Kruskal MST、Djikstra's algorithm Dynamic Programming( 動態演算法 ) 二項是係數 、 矩陣連乘、最佳二元搜尋樹 … Trace Back( 回溯 ) 圖形著色、漢米爾迴路問題 …. Branch-and-Bound ( 樹的追蹤 ). 簡介. - PowerPoint PPT Presentation

Citation preview

Page 1: 回溯演算法 (Backtracking)

回溯演算法 (Backtracking)

Page 2: 回溯演算法 (Backtracking)

2

The Divide-and-Conquer Strategy ( 個各擊破 ) binary Searching 、 Quick Sort….

The Greedy Method( 貪婪演算法 ) Prim MST 、 Kruskal MST 、 Djikstra's algorithm

Dynamic Programming( 動態演算法 ) 二項是係數、矩陣連乘、最佳二元搜尋樹…

Trace Back( 回溯 ) 圖形著色、漢米爾迴路問題… .

Branch-and-Bound ( 樹的追蹤 )

Page 3: 回溯演算法 (Backtracking)

3

簡介簡介回溯法通常被用來解下面這類型問題。問題敘述:你必須從一個物件集合中選出一序列序列的物件,並且這序列要滿足一些指定條件指定條件。例如, n-Queen 問題。必須要將 n 個皇后放在一個 nn 的西洋棋盤上而不互相攻擊。序列:安全地擺皇后的 n 個位置。物件集合:所有可能的 n2個棋盤上的位置。

Page 4: 回溯演算法 (Backtracking)

4

回溯技巧:深度優先搜尋 (Depth-first search) 演算法之變形

Page 5: 回溯演算法 (Backtracking)

5

1

2 8 11

3 4 7

5 6

9 10 12 13 16

14 15

Depth-First Search

Page 6: 回溯演算法 (Backtracking)

6

depth_first_tree_search Algorithm

void depth_first_tree_search ( node v ){

node u ;visit v ; // 走訪 vfor ( v 的每個子節點 u ) // 由左到右依序走訪

depth_first_tree_search ( u ) ;}

Page 7: 回溯演算法 (Backtracking)

7

生成樹V1 V2

V4V3

V5

Page 8: 回溯演算法 (Backtracking)

8

n=4 的 n-Queen 問題后后

后后

若將第一個皇后放在第一行,則第二個皇后不可以放在第一行或是第二行。

Page 9: 回溯演算法 (Backtracking)

9

Start

1,1 1,2 1,3 1,4

2,12,1 2,2 2,3 2,4

3,1 3,2 3,3 3,4

4,1 4,2 4,3 4,4 4,1 4,2 4,3 4,4

4-Queen 問題的部份狀態空間樹<i, j> 表示第 i 列的皇后放在第 j 行。

任何一條從樹根到葉節點的路徑就代表著一個可能的答案。

Page 10: 回溯演算法 (Backtracking)

10

回溯法的定義1. 當我們知道此節點一定會通往死路時,我們就立即返回父節點,繼續走訪父節點的其它子節點。2. 沒前景 (nonpromising) :某節點一定無法帶我們找到答案。3. 有前景 (promising) :某節點可能帶我們找到答案。

Page 11: 回溯演算法 (Backtracking)

11

回溯法的定義 ( 續 )1. 修剪 (pruning) 狀態空間樹:走到 nonpromising 節點時,立刻返回父節點的動作。2. 修剪過的狀態空間樹 (pruned state space tr

ee) :修剪後剩下的那些走訪過的節點。

Page 12: 回溯演算法 (Backtracking)

12

回溯的一般演算法 checknode

void checknode ( node v ){

node u ;if ( promising ( v ) )

if ( v 是答案 )印出答案 ;else

for ( v 的每個子節點 u )checknode ( u ) ;

}

針對不同問題設計

Page 13: 回溯演算法 (Backtracking)

13

Page 14: 回溯演算法 (Backtracking)

14

Page 15: 回溯演算法 (Backtracking)

15

4-Queen 的回溯演算法Start

1,1

2,1 2,2 2,3 2,4

3,1 3,2 3,3 3,4 3,1 3,2 3,3 3,4

4,1 4,2 4,3 4,4

1,2

2,1 2,2 2,3 2,4

3,1

4,1 4,2 4,3

后后 后

后 后 后 后后 后

后后 后 后 后后

遇到 nonpromosing 節點就立即返回父節點,並走訪父節點的其他子節點。

Page 16: 回溯演算法 (Backtracking)

16

用 expand 來改進 checknode 的效率void expand ( node v ){

node u ;for ( v 的每個子節點 u )

if ( promising ( u ) )if ( 在 u 有解答 )印出解答 ;else

expand ( u ) ;}

先檢查再走訪為何這樣較有效率?

Page 17: 回溯演算法 (Backtracking)

17

The n-Queens ProblemCol(i) : 第 i 列皇后所在的行位置。Col(3) = 1, Col(6) = 4Col(6) - Col(3)= 4 - 1 = 3 ( = 6 - 3 )

Col(2) = 8, Col(6) = 4Col(6) - Col(2) = 4 - 8 = -4 ( = 2 - 6 )可改寫成 :| Col(6) - Col(2) |= 4 ( = 6 - 2 )

Page 18: 回溯演算法 (Backtracking)

18

void queens ( index i ) { index j ; if ( promising (i) ) if ( i == n ) cout << col [1] 至 col [n] ; else for ( j = 1; j <= n ; j++ ) { col [i + 1] = j ; // 檢查位於第 (i+1) 列的 queens (i + 1); // 皇后可否放在第 j 行上 } }

The Backtracking Algorithm for the n-Queens Problem

Page 19: 回溯演算法 (Backtracking)

19

bool promising ( index i ) { index k ; bool switch ; k = 1 ; switch = true ; // 檢查有沒有其他皇后會攻擊while ( k < i && switch ){ // 第 i 列的皇后if (col [i] == col [k] || abs (col [i] - col [k]) == i - k) switch = false ; k++; } return switch ; }

The Backtracking Algorithm for the n-Queens Problem

Page 20: 回溯演算法 (Backtracking)

20

分析

111

132

nnnnnnn

n

整個狀態空間樹的節點總數為:

這就是我們所需走訪的節點數上限。n = 8 時, 個節點961,173,19

1818 18

Page 21: 回溯演算法 (Backtracking)

21

分析嘗試分析 promising 節點數的上限值,以 n=8 狀況來說,第一個皇后可以放在任何一行上面,而第二個皇后只剩下七個行位置可以選,以此類推,第八個皇后只剩下一個行位置可以選。

601,109!856786787881

公式的一般式為:!)2)(1()1(1 nnnnnnn 個 promising 節點

夠精準嗎?對角線檢查呢?直接看看執行時所走訪的節點數。

Page 22: 回溯演算法 (Backtracking)

22

列出使用回溯演算法來解決 n- 皇后問題所能避免檢查的節點數N 演算法一所走訪的節點數 演算法二所走訪的節點數 回溯走訪的節點數

回溯找到的 promising 節點數

4 341 24 61 17

8 19,173,961 40,320 15,721 2057

12 9.731012 4.79108 1.01107 8.56105

14 1.201016 8.72108 3.78108 2.74107

演算法二只考慮行和列是否遭受攻擊的情況,故需走訪 n ! 個節點。

由 promising 知,expand 函式可節省相當多時間。

DFS

Page 23: 回溯演算法 (Backtracking)

23

演算法分析的目的‧ 是要在程式執行之前就判斷演算法的效率。‧ 給定兩個大小相同 ( n 值相等 ) 的不同問題,其中一 個可能只需走訪很少節點,另一個可能需要走訪所 有節點。如何知道回溯法對該問題是否有效?

Page 24: 回溯演算法 (Backtracking)

24

Sum-of-Subsets 問題範例 5.2 找出所有重量和為 W 的子集合假設 n = 5 , W = 21 且

w1=5 w2=6 w3=10 w4=11 w5=16因為w1+w2+w3 = 5+6+10 = 21 w1+w5 = 5+16 = 21 w3+w4 = 10+11 = 21

所以解答為 { w1 , w2 , w3 } 、 { w1 , w5 } 、 { w3 , w4 }

在 0-1 背包問題中,只要找到一組解,就滿足小偷的要求。

Page 25: 回溯演算法 (Backtracking)

25圖 5.7 Sum-of-Subsets 問題在 n = 3 時的狀態空間樹

n 很大時,可建立狀態空間樹

Page 26: 回溯演算法 (Backtracking)

26

n=3 , W=6 的狀態空間樹

唯一解

Page 27: 回溯演算法 (Backtracking)

27

Sum-of-Subsets 的回溯策略‧ 事先將所有重量以遞增方式排序。‧ 設 wi+1 是第 i 層節點中剩下的最輕物品。 設 weight = 到第 i 層節點時的物件重量總和。 nonpromising 檢驗策略:

weight + wi+1 > W

‧ 設 total 代表剩下物件的總重量。 nonpromising 檢驗策略:

weight + total < W

Page 28: 回溯演算法 (Backtracking)

28

展示使用回溯法來處理 n=4 、 W=13

所有不含解答的葉節點都是 nonpromising

一定要走到葉節點才會有解嗎?

Page 29: 回溯演算法 (Backtracking)

29

用回溯解決 Sum-of-Subsets 問題問題:給定 n 個正整數 ( 重量 ) 和另一個正整數 W ,找出所有重量 和是 W 的正整數集合。輸入:正整數 n ,已經排序過的遞增正整數 w ,正整數 W 。輸出:所有重量總和為 W 的正整數集合。void sum_of_subsets ( index i , int weight , int total ){ if ( promising ( i ) ) // 檢查第 i 個物品是否 promising

if ( weight == W ) cout << include[1] 到 include [i] ; // 回溯else {

include[i + 1] = "yes" ; // 選取 w[i + 1] 物品,展開左子樹sum_of_subsets ( i+1 , weight + w[i+1], total - w[i+1] );include[i + 1] = "no" ; // 不選取 w[i + 1] 物品,展開右子樹sum_of_subsets ( i+1 , weight , total - w[i+1] ) ;

}}

Page 30: 回溯演算法 (Backtracking)

30

bool promising ( index i ){

return ( weight + total >= W ) &&( weight == W || weight + w[i+1] <= W ) ;

}

(續 )

promising 策略:(目前已選取物品總重量 + 剩下物品總重量 >= W)且(目前已選取物品總重量 == W) 或(目前已選取物品總重量 + 剩下物品中最輕的重量 <= W )

Page 31: 回溯演算法 (Backtracking)

31

狀態空間樹的節點數

總共節點數 = 1+2+22+...+2n = 2n+1 - 1(參考 A.3)

必須用 Monte Carlo 來分析才有辦法評估效率。

Page 32: 回溯演算法 (Backtracking)

32

圖形著色V1 V2

V3V4

本圖無 2- 著色問題的解,但有 3- 著色問題的解 (6 個 ) 。m- 圖形著色問題的定義:每個相鄰節點不可用相同的顏色來著色。最多用 m 種顏色。不同 m 值的問題視為彼此單獨不同的問題。

Page 33: 回溯演算法 (Backtracking)

33

平面圖形 (Planar)

一個圖形可在平面上著色且任何節線不相交,即稱為 Planar 。地圖 Planar

加上 (V1 , V5) 和 (V2 , V4)後就不再是 Planar 。

Page 34: 回溯演算法 (Backtracking)

34

0 1 1 1

1 0 1 0

1 1 0 1

1 0 1 0

相鄰矩陣

V1 V2

V3V4

使用回溯來解決 3- 著色問題

第一個解答

Page 35: 回溯演算法 (Backtracking)

35

解 m- 著色問題 ( 回溯演算法 )

問題:找出所有可能方式,只用 m 種顏色來對一個無向圖 著色,並使得任兩相鄰頂點均為相異色。輸入:正整數 n 和 m ,一個有 n 個頂點的無向圖形 ( 以相鄰 矩陣表示之 ) 。輸出:所有可能的方法,最多用 m 種顏色。 著色結果存在索引為 1 到 n 的 vector 陣列中, vector[i]代表的就是第 i 個頂點的顏色 (正整數 1 到 m) 。

Page 36: 回溯演算法 (Backtracking)

36

void m_coloring ( index i ){

int color ;if ( promising ( i ) )

if ( i == n ) cout << vector[1] 到 vcolor[n] ;else for ( color = 1 ; color <= m ; color ++ ) {

vcolor[i+1] = color ; // 對下個頂點嘗試m_coloring( i+1 ) ; // 著每種顏色

}}

Page 37: 回溯演算法 (Backtracking)

37

bool promising ( index i ){

index j ;bool switch ;

switch = true ;j = 1 ;while ( j < i && switch ) {

if ( W[i][j] && vcolor[i] == vcolor[j] )switch = false ; // 檢查相連頂點是否有

j ++ ; // 相同顏色}return switch ;

}

Page 38: 回溯演算法 (Backtracking)

38

漢米爾頓迴路問題V1 V2 V3 V4

V5V6V7V8

V1V2V8V7V6V5V4V3V1

V1 V2 V3

V4V5

找不到任何一條漢米爾頓迴路

Page 39: 回溯演算法 (Backtracking)

39

漢米爾頓迴路的回溯策略1. 路徑上第 i 個點在圖形上必須與路徑上第 i-1 個點相連。2. 路徑上第 n-1 個點在圖形上必須與路徑上第 0 個點相連。3. 路徑上第 i 個點不可以與路徑上的前 i-1 個點重複。若不符合上述三個條件要求,則立即回溯。在演算法中,強制規定用 V1 當作路徑的起始點。

Page 40: 回溯演算法 (Backtracking)

40

用回溯法來解漢米爾頓迴路問題問題:在一個無向圖中找出所有的漢米爾頓迴路。輸入:正整數 n 。 一個有 n 頂點數的無向圖,用一個二維陣列 W 表示。 行列編號皆由 1 至 n 表示。若 W[i][j] = true ,表示頂 點 i 和頂點 j 之間有邊相連接著。輸出:找出所有從某起始點開始,經過其它頂點僅一次,然 後回到原起始點的路徑。將結果存放在 vindex 陣列中 ,索引為 1 到 n-1 , vindex[i] 代表路徑上的第 i 個點。 路徑的起始點為 vindex[0] 。

Page 41: 回溯演算法 (Backtracking)

41

void hamiltonian ( index i ){ index j ; if ( promising ( i ) ) // 檢查路徑上的第 i 點是否有前景

if ( i == n-1 ) 列印出 vindex[0] 至 vindex[n-1] 之值 ; else

for ( j=2 ; j <= n ; j ++ ) //拿所有頂點當路徑的下一點 vindex[ i+1 ] = j ; hamiltonian ( i+1 ) ;

}}

初始呼叫方式: vindex[0] = 1; //讓 V1 成為開始的頂點 hamiltonian ( 0 ) ;

Page 42: 回溯演算法 (Backtracking)

42

bool promising ( index i ){ index j ; bool switch ; if ( i == n-1 && ! W[vindex[n-1]][vindex[0]] )

switch = false ; // 最後一點和第一點必須相連 else if ( i > 0 && ! W[vindex[i-1]][vindex[i]] )

switch = false ; // 第 i 點必須和第 (i-1) 點相連 else {

switch = true ;j = 1 ;while ( j < i && switch ) { // 檢查這點是否出現過

if ( vindex[i] == vindex[j] )switch = false ;

j ++ ;}

} return switch ;}

Page 43: 回溯演算法 (Backtracking)

43

本演算法的狀態空間樹的總共節點數為

21)1()1()1()1(1 12

nnnnn

nn

Page 44: 回溯演算法 (Backtracking)

44

用回溯法解 0-1 背包問題Sum-of-Subsets 問題只要找到一組重量等於 W 的解答即可。但 0-1 背包問題則是除了重量等於 W 之外,還要找到獲得利益最大的那組解答,所以一定要搜尋完整個狀態空間樹的所有節點才行。void checknode ( node v ){ node u ; if ( value ( v ) 比 best 更好 ) best = value ( v ) ; if ( promising ( v ) )

for ( 每個 v 的子節點 u )checknode ( u ) ;

}

初始時,給定一個最差值給 best 。只要可以擴展子節點,就是 promising 。

Page 45: 回溯演算法 (Backtracking)

45

回溯法解 0-1 背包問題的策略(1) 由樹的根節點往下走到第 i 層節點時,若已經沒空間 再放入更多物品的話,則該節點就是 nonpromising 。 設 weight = 在第 i 層節點所累積的物品總重量若

weight W

則此節點一定是 nonpromising 。背包的最大承受重量

Page 46: 回溯演算法 (Backtracking)

46

回溯法解 0-1 背包問題的策略( 續 )

(2) 從貪婪法則的觀點來找出一個較不明顯的判斷 promising 與否的方法。

1

1

k

ijjwweighttotweight

k

kk

ijj w

PtotweightWpprofitbound

1

1

前 k-1 個物品的總獲益可以分配給第 k 個物品的容量

第 k 個物品單位重量的獲益

總重量到第 i 層節點為止的總重量

第 i+1 層到 k-1 層節點為止的總重量。因為到第 k 層就爆了。

第 i 層節點的最大獲益上限值。

第 1 到 i 層節點的總獲益值

Page 47: 回溯演算法 (Backtracking)

47

第 i 層節點若滿足下面條件,則為 nonpromising :bound maxprofit

到目前為止所找到的最好的獲益值。第 i 層節點的最大獲益能力。

Page 48: 回溯演算法 (Backtracking)

48

範例 n=4 , W=16i Pi wi Pi/wi

1 $40 2 $202 $30 5 $63 $50 10 $54 $10 5 $2

將物品依照單位重量獲益比 (Pi / Wi ) 值由大到小排序。狀態空間樹的每個節點內部有三個數字,由上而下為:全部獲益 ( 即 profit )全部重量 ( 即 weight )獲益上限值 ( 即 bound ) 下一頁

Page 49: 回溯演算法 (Backtracking)

49

回溯法解範例的修剪過的狀態空間樹全部獲益 profit

全部重量 weight

最大獲益能力 bound物品價值物品重量

maxprofit=0

maxprofit=40

maxprofit=70

maxprofit=70

maxprofit=70

maxprofit=80

maxprofit=80

maxprofit=80

maxprofit=90

maxprofit=90

maxprofit=90

maxprofit=90

maxprofit=90

下一頁上一頁

Page 50: 回溯演算法 (Backtracking)

50

(0,0) 節點計算過程1. 將 maxprofit 設為 0 。 ( 指定一個最差值給目前最佳獲益值 )2. 走訪 (0, 0) 節點,即根節點。 (a) 計算它的 profit 和 weight 。

profit = $0weight = 0

(b) 計算它的 bound 值。2+5+10 = 17 且 17 > 16 ( 背包最大載重量 W)所以加入第 3 個物品後就會使總重量超過 W 。=> k = 3 ( 前面投影片中公式裡的 k 值 )

13

10

7520j

jwweighttotweight

圖 5.4

Page 51: 回溯演算法 (Backtracking)

51

(0,0) 節點計算過程 ( 續 )

115$1050$)716(30$40$0$

)(13

10 3

3

jj w

ptotweightWpprofitbound

判斷出本節點 (0, 0) 是 promising 。因為 (1) weight = 0 小於 16 (W 的值 ) (2) bound = $115 大於 $0 (目前 maxprofit 的值 )

P1 P2 第 3 個物品的部份獲益值

圖 5.4

Page 52: 回溯演算法 (Backtracking)

52

回溯法解 0-1 背包問題問題:給定 n 個物件及其個別 weight 與 profit 值 (皆為正整數 ) 。 給定 W 值。在總重量不超過 W 的條件下,找出一組物件 使其總獲益是最大的。輸入:正整數 n 和 W 。 陣列 w 與 p (索引均由 1 到 n ,且根據 p[i]/w[i] 由大到小 排序過 ) 。輸出: bestset 陣列 (索引由 1 到 n) ,其中 bestset[i] 值是 yes 代表 要拿第 i 個物品, no 代表不拿第 i 個物品。 正整數 maxprofit ( 即最大獲益 ) 。

Page 53: 回溯演算法 (Backtracking)

53

void knapsack ( index i , int profit , int weight ){ if ( weight <= W && profit > maxprofit ) { // 若總重可接受,且獲益更好

maxprofit = profit ; // 將最佳獲益值 maxprofit 設成現在這個numbest = i ; // 將 numbest 設成目前考慮的物品數bestset = include ; // 將 bestset 設成這個解

}

if ( promising ( i ) ) { // 判斷第 i 層節點可否往下擴展include [i+1] = "yes" ; //拿 w[i+1]knapsack ( i+1 , profit + p[i+1] , weight + w[i+1] ) ;include [i+1] = "no" ; // 不拿 w[i+1]knapsack ( i+1 , profit , weight ) ;

}}

Page 54: 回溯演算法 (Backtracking)

54

bool promising ( index i ){ index j , k ; int totweight ; float bound ; // 第 i 層節點最大獲益能力 if ( weight >= W ) return false ; // 背包已經爆了 else {

j = i + 1 ; // 從 i 的下一層物品一直拿到背包爆掉或無東西可拿為止bound = profit ;totweight = weight ;while ( j <= n && totweight + w[j] <= W ) {

totweight = totweight + w[j] ; //盡可能拿越多物品越好bound = bound + p[j] ; // 每拿一個物品就增加最大獲益能力j ++ ;

}k = j ; // 在拿第 k 層物品時背包爆掉了 ( 或東西拿光了 )if ( k <= n ) bound = bound + (W-totweight) * ( p[k] / w[k] ) ;return bound > maxprofit ; //目前獲益值比最佳值更好則 promising

} //反之,則代表第 i 層節點是 nonpromising}

第 k 個物品只拿一部份

Page 55: 回溯演算法 (Backtracking)

55

下面的程式可輸出最佳獲益值 maxprofit 和對應的物件集合。numbest = 0 ; //稍後用來存放最佳解的物件個數maxprofit = 0 ;knapsack ( 0, 0, 0 ) ;cout << maxprofit ; // 印出最佳獲益值for ( j = 1 ; j <= numbest ; j++ ) // 印出最佳解的物件集合 cout << bestset[j] ;