1. 程式人生 > >分支限界法與回溯法的區別

分支限界法與回溯法的區別

分支限界法類似於回溯法,也是一種在問題的解空間樹T上搜索問題解的演算法。但在一般情況下,分支限界法與回溯法的求解目標不同。回溯法的求解目標是找出T中滿足約束條件的所有解,而分支限界法的求解目標則是找出滿足約束條件的一個解,或是在滿足約束條件的解中找出使某一目標函式值達到極大或極小的解,即在某種意義下的最優解。 

由於求解目標不同,導致分支限界法與回溯法在解空間樹T上的搜尋方式也不相同。回溯法以深度優先的方式搜尋解空間樹T,而分支限界法則以廣度優先或以最小耗費優先的方式搜尋解空間樹T。分支限界法的搜尋策略是:在擴充套件結點處,先生成其所有的兒子結點(分支),然後再從當前的活結點表中選擇下一個擴充套件對點。為了有效地選擇下一擴充套件結點,以加速搜尋的程序,在每一活結點處,計算一個函式值(限界),並根據這些已計算出的函式值,從當前活結點表中選擇一個最有利的結點作為擴充套件結點,使搜尋朝著解空間樹上有最優解的分支推進,以便儘快地找出一個最優解。
分支限界法常以廣度優先或以最小耗費(最大效益)優先的方式搜尋問題的解空間樹。問題的解空間樹是表示問題解空間的一棵有序樹,常見的有子集樹和排列樹。在搜尋問題的解空間樹時,分支限界法與回溯法對當前擴充套件結點所使用的擴充套件方式不同。在分支限界法中,每一個活結點只有一次機會成為擴充套件結點。活結點一旦成為擴充套件結點,就一次性產生其所有兒子結點。在這些兒子結點中,那些導致不可行解或導致非最優解的兒子結點被捨棄,其餘兒子結點被子加入活結點表中。此後,從活結點表中取下一結點成為當前擴充套件結點,並重覆上述結點擴充套件過程。這個過程一直持續到找到所求的解或活結點表為空時為止。 有一些問題其實無論用回溯法還是分支限界法都可以得到很好的解決,但是另外一些則不然。也許我們需要具體一些的分析
——到底何時使用分支限界而何時使用回溯呢?下表列出了回溯法和分支限界法的一些區別:
方法 對解空間樹的搜尋方式 儲存結點的常用資料結構 結點儲存特性 常用應用
回溯法 深度優先搜尋 堆疊 活結點的所有可行子結點被遍歷後才被從棧中彈出 找出滿足約束條件的所有解
分支限界法 廣度優先或最小消耗優先搜尋 佇列、優先佇列 每個結點只有一次成為活結點的機會 找出滿足約束條件的一個解或特定意義下的最優解
A、一個比較適合採用回溯法解決的問題——n後問題 n*n的國際象棋棋盤上擺下n個後,使所有的後都不能攻擊到對方,找出所有符合要求的情況。 n後問題的解空間樹是一棵排列樹,一旦一種組合是一種解,則解與解之間不存在優劣的分別。直到搜尋到葉節點時才能確定出一組解。這時我們用回溯法可以系統地搜尋問題的全部解,而且由於解空間樹是排列樹的特性,程式碼的編寫十分容易,在最壞的情況下,堆疊的深度不會超過
n。如果我們採取分支限界法,在解空間樹的第一層就會產生n個活結點,如果不考慮剪枝,將在第二層產生n*(n-1)個活節點,如此下去對佇列空間的要求太高。n後問題不適合使用分支限界法處理的根源是它需要找處所有解的組合,而不是某種最優解(事實上也沒有最優解可言)。形象一點來說,如果讓我們自己人工解決n後問題,我們的思路應該是放一個後,看衝不衝突,不衝突的話放下一個,衝突的話改變位置,這樣可以只用一個棋盤而且有規律地找到所有符合條件的情況,這正是回溯法的模擬過程。然而分支限界法則可以被這樣表述。拿來一個棋盤,擺下一隻後;再拿一個棋盤,再擺一隻;待到每個棋盤都有一隻後以後,每個棋盤又被分解成更多的盤面...,這樣,棋盤越來越多,但是由於解和解(包括區域性解)之間缺乏因果和限制的聯絡。棋盤之間並不能根據對方的資訊獲得什麼,這顯然是對資源的浪費。
B、一個既可以採用回溯法也可以採用分支限界法解決的問題——0-1揹包問題 給定若干物品的重量和價值,以及一個揹包的容量上限。求出一種方案使的揹包中存放物品的價值最高。這個問題除了可以考慮回溯法和分支限界法之外,還可以用動態規劃的方法解決,但在這裡我們主要討論前兩種方法的對比。 0-1揹包問題的解空間樹是一棵子集樹,所要求的解具有最優性質。 如果我們採用回溯法解決這個問題,我們採用如下的搜尋策略:只要一個結點的左兒子結點是一個可行結點就搜尋其左子樹;而對於右子樹,我們需要用貪心演算法構造一個上界函式[[這個函式表明這個結點的子樹所能達到的可能的最大容量(因為只有將0-1揹包問題改變為揹包問題才可能利用貪心演算法,因此這個上界函式在絕大多數情況下不會是上確界函式)],只在這個上界函式的值超過當前最優解時才進入搜尋。隨著搜尋程序的推進,最優解不斷得到加強,對搜尋的限制就越來越嚴格。 如果我們採用分支限界法解決這個問題,同樣需要用到貪心演算法構造的上界函式,所不同的是,這個上界函式的作用不在於判斷是否進入一個結點的子樹繼續搜尋,因為在搜尋到達葉節點之前,我們也無法知道已經得到的最優解是什麼。在這裡,我們用一個最大堆來實現活結點的優先佇列,上界函式的值將作為優先順序,這樣一旦有一個葉結點成為擴充套件結點,就表明已經找到了最優解。 可以看出,用兩種方法處理0-1揹包問題都有一定的可行性,相比之下回溯法的思路容易理解一些,但是這是一個尋找最優解的問題,由於採用了優先佇列處理,不同的結點不再像n後問題那樣沒有相互之間的牽制和聯絡,用分支限界法處理效果一樣很好。 C、一個比較適合採用分支限界法解決的問題——佈線問題 印刷電路板將佈線區域劃分成n*m個方格陣列。精確的電路佈線問題要求確定連線方格a的中點到方格b的中點的最短佈線方案。在佈線時,電路只能沿直線或直角佈線。為了避免線路相交,已布了線的方格做了封鎖標記,其他線路不允許穿過被封鎖的方格。 佈線問題的解空間是一個圖,適合採用佇列式分支限界法來解決。從起始位置a開始將它作為第一個擴充套件結點。與該結點相鄰並且可達的方格被加入到活結點佇列中,並且將這些方格標記為1,表示它們到a的距離為1。接著從活結點佇列中取出隊首作為下一個擴充套件結點,並將與當前擴充套件結點相鄰且未標記過的方格標記為2,並存入活節點佇列。這個過程一直繼續到演算法搜尋到目標方格b或活結點佇列為空時為止(表示沒有通路)。 現在來看上面兩張圖。左圖演示了分支限界法的過程。最開始,佇列中的活結點為標1的格子,隨後經過一個輪次,活結點變為標2的格子,以此類推,一旦b方格成為活節點便表示找到了最優方案。為什麼這條路徑一定就是最短的呢?這是由於我們這個搜尋過程的特點所決定的,假設存在一條由ab的更短的路徑,b結點一定會更早地被加入到活結點佇列中並得到處理。 右圖表示了ab之間的最短佈線路徑。值得一提的是,在搜尋的過程中我們雖然可以知道結點距起點的路徑長度,卻無法直接獲得具體的路徑描述。為了構造出具體的路徑,我們需要從目標方格開始向起始方格回溯,逐步構造出最優解,也就是每次向標記距離比當前方格距離少1的相鄰方格移動,直至到達起始方格時為止。 現在我們來考慮,為什麼這個問題用回溯法來處理就是相當低效的。回溯法的搜尋是依據深度優先的原則進行的,如果我們把上下左右四個方向規定一個固定的優先順序去進行搜尋,搜尋會沿著某個路徑一直進行下去直到碰壁才換到另一個子路徑,但是我們最開始根本無法判斷正確的路徑方向是什麼,這就造成了搜尋的盲目和浪費。更為致命的是,即使我們搜尋到了一條由ab的路徑,我們根本無法保證它就是所有路徑中最短的,這要求我們必須把整個區域的所有路徑逐一搜索後才能得到最優解。正因為如此,佈線問題不適合用回溯法解決。 還有許多類似於佈線的問題可以為分支限界法提供良好的展示空間。在今年10月底剛剛結束的ACM競賽北京分賽區的比賽中,就出現一一個和佈線問題十分相似的問題,可以用分支限界法取得很好的效果。 我們簡單地描述一下這個問題。如左圖,B1,B2,B3,B4代表一條蛇的身軀,其中B1是蛇頭(蛇的身軀大小、起始位置以及障礙物的位置由輸入獲得),同樣是在一個範圍內搜尋兩個固定點蛇頭和點(11)之間的最短路徑。題目的要求相比於佈線問題有一些簡化的地方,比如終點的位置是固定的,而且只要求求出最短路徑的長度,不要求寫出具體的路徑,也就是可以省出回溯起點的步驟,但是它也有一個需要做一定處理才能解決的推廣,就是蛇所要移動的方向上除了不能是障礙物之外,還不能是自己身軀的一部分,比如蛇從左圖的位置移動,不能向上或向右移動而只能移動到下方B1的位置形成右圖的局面。 要做如何的處理才能解決這個問題呢?可以採取佈線問題的解決框架,但是在每個結點處所出現的障礙物需要變化。如果我們僅僅在每個結點處把蛇身標記為與障礙物相同的記號固然是一種容易想到的思路,但是這樣處理不利於從一個格子的障礙物情形推廣到相鄰格子的障礙物情形。比較有效的做法是將蛇身描述成一個定長佇列,佇列首是蛇尾而佇列尾是蛇頭(如果為了思路的連貫性,也可以反過來處理,但是這樣應該使用雙向佇列),這樣一來從一個方格推廣到相鄰方格的時候,我們只要對這個佇列做一步處理就可以得到新的描述蛇身的佇列。然後,可以用這個佇列和方格本身的位置組成一個結構或類,在求解過程中依然使用佇列式分支限界法,只不過佇列中的元素是我們所定義的那個結構或類的物件。利用STL等一些泛型技術,這個過程還是比較容易實現的。
struct Position//描述一個位置的結構
{

int posx,posy
 
}
struct SnakeNode//描述分支限界法佇列中一個結點的結構
{

Position square
//方格位置資訊list<Position> snakeBody(snakeLength)//描述蛇身的佇列,每一個格的蛇仍用Position描述
}
這樣初始時我們用queue<SnakeNode> nodeList(0)建立這個佇列,之後的過程和佈線問題就基本一致了。