回溯法是深度優先策略遍歷問題的解空間樹。分支限界法按廣度優先策略遍歷問題的解空間樹,在遍歷過程中對已經處理的每一個節點根據銜接函式估算目標函式的可能取值,從中選取使目標函式取得極值(極大或極小)的節點優先進行廣度優先搜尋,從而不斷調整搜尋方向,儘快找到問題的解。
概述
解空間樹的動態搜尋
分支限界法首先確定一個合理的限界函式,並根據限界函式確定目標函式的界[down,up],然後,按照廣度優先策略遍歷問題的解空間樹,在分支節點上,一次搜尋該節點的所有孩子節點,分別估算這些孩子節點的目標函式的可能取值,如果某孩子節點的目標函式可能取得的值超出目標函式的界,則將其丟棄,因為從某個節點生成的解不會比目前已經得到的解更好;否則,將其加入待處理節點表(簡稱為PT)中。依次從表PT中選取使目標函式的值取得極值的節點成為當前的擴充套件節點,重複上述過程,直到找到最優解。
使用分支限界法求解0/1問題
- 按照物品單位重量價值從大到小排序
- 目標函式:ub=v + (W - w)*(vi+1/wi+1),代表價值的上限
- 從物品1開始,不斷選擇物品放入揹包,需要檢查空間是否合適,如果空間合適,計算目標函式和節點一同放入PT中。然後從PT中選擇目標函式最大的節點,繼續選擇物品,直到計算出結果。
分支限界法的設計思想
假設求解最大化問題,問題的解向量為X=(x1,x2,……,xn),其中,xi的取值範圍為某個有窮集合Si,|Si|=ri(1<=i<=n)。在使用分支限界法搜尋問題的解空間樹時,首先根據限界函式估算目標函式的界[down,up],然後從根節點出發,擴充套件根節點的r1個孩子節點,從而構成分量x1的r1種可能的取值方式。對這r1個孩子幾點分別估算可能取得的目標函式值bound(x1),其含義是以該孩子節點為根的子樹鎖可能取得的目標函式值不大於bound(x1),也就是部分解應滿足:
bound(x1)>=bound(x1,x2)>=……>=bound(x1,x2,……,xn)
若某孩子節點的目標函式值超出目標函式的界,則將該孩子節點丟棄;否則,將該孩子節點儲存在待處理節點表PT中。從表PT中選取使目標函式取得極大值的節點作為下一次擴充套件的根節點,重複上述過程,當到達一個葉子節點時,就得到了一個可行解X=(x1,x2,……,xn)及其目標函式值bound(x1,x2,……,xn)。如果bound(x1,x2,……,xn)是表PT中目標函式值最大的節點,則bound(x1,x2,……,xn)就是所求問題的最大值,(x1,x2,……,xn)就是問題的最優解;如果bound(x1,x2,……,xn)不是表PT中目標函式值最大的節點,說明還存在某個部分解對應的節點,其上界大於bound(x1,x2,……,xn)。於是,用bound(x1,x2,……,xn)調整目標函式的下界,即令down=bound(x1,x2,……,xn),並將表PT中超出目標函式下界down的節點刪除,然後選取目標函式值取得極大值的節點作為下一次擴充套件的根節點,繼續搜尋,直到某個葉子節點的目標函式值在表PT中最大。
這個設計思想講述了分支限界法的本質,大家一定要充分的理解。
應用分支限界法的關鍵問題是:
-
如何確定合適的限界函式
分支限界法在遍歷過程中根據限界函式估算某節點的目標函式的可能取值,一定要設計出好的限界函式。
-
如何組織待處理節點表
表PT可以採用堆的形式,也可以採用優先佇列的形式
-
如何確定最優解中的各個分量
- 對每個擴充套件節點儲存根節點到該節點的路徑
- 在搜尋過程中構建搜尋經過的樹結構,在求得最優解時,從葉子節點不斷回溯到根節點,以確定最優解中的各個分量。
圖問題中的分支限界法
TSP問題
TSP問題是指旅行家要旅行n個城市,要求各個城市經歷且僅經歷一次,然後回到出發城市,並要求所走的路程最短。 一般情況下,對於一個正在生成的路徑,假設已確定的頂點集合U=(r1,r2,……,rk),即路徑上已確定了k個頂點,此時,該部分解的目標函式值的計算方法如下:
所以lb的下限為((1+3)+(3+6)+(1+2)+(3+4)+(2+3))/2=14,上限使用貪心法,計算出數值為16,所以限界函式的界為[14,16]。
為什麼限界函式是這樣的,是因為
- 容易計算,因為每個點都有一條出邊,一條入邊,計算的時候兩倍相加,然後相除,計算起來比較容易
- 可以計算出下限,如果有邊(1,3),那麼1至3的距離確定了,但是1需要有一條出去的邊,一條回來的邊,所以,下限是除了到3距離之外的,最小的另一條邊。
解題過程和虛擬碼如下:
- 107. 二叉樹的層次遍歷 II - 簡單 程式碼 這是最簡單的廣度優先遍歷,都沒有用到限界函式
多段圖的最短路徑問題
設圖G=(V,E)是一個帶權有向連通圖,如果把頂點集合V劃分成k個互不相交的子集Vi(2<=k<=n,1<=i<=k),使得E中的任何一條邊(u,v),必有u屬於Vi,v屬於Vi+m(1<=i<k,1<i+m<=k),則稱圖G為多段圖,稱s屬於V1為源點,t屬於Vk為終點。
-
確定上下界,下界計算每一段的最小值,2+4+5+3=14,上界使用貪心法0->2->5->8->9,路徑代價為18,所以目標函式界為[14,18]
-
假設已經確定了i段,其路徑為(r1,r2,……,ri,ri+1),此時限界函式: 該公式含義是將已經確定的路段相加,然後找到下一個路段最短的連線,然後將再後面每組最短邊相加。例如,如果已經確定了走邊(0,1),則目標函式為4(已確定的邊)+8(從1到4、5的最短邊)+5(第三組最小邊)+3(第四組最小邊)
-
每次選取邊,如果lb超出了18,則捨棄,如果在範圍之內,則放入以lb位置的最小堆結構中,每次選取lb最小節點,進行廣度優先遍歷,直到獲得最終結果。
例題:
組合問題中的分支限界法
任務分配問題
任務分配問題要求把n項任務分配給n個人,每個人完成每項任務的成本不同,要求分配總成本最小的最優分配分案。
-
確定上下界,下界是每一行最小值2+3+1+4=10,上界使用貪心法計算2+3+5+4=14,所以範圍為[10,14]
-
限界函式:設當前已對人員1~i分配了任務,並且獲得了成本v,則限界函式可以定義為,
-
使用廣度優先遍歷,計算lb,如果lb超過上界,則丟棄,如果在範圍內,放入最小堆中,每次從中選取最小的lb進行廣度優先遍歷,直到計算完所有值。
例題
- 690. 員工的重要性 - 簡單 程式碼 這道題也沒有用到限界函式,也沒有用堆做排序,例題較少
批處理作業排程問題
給定n個作業的集合J={J1,J2,……,Jn},每個作業都有3項任務分別在3臺機器上完成,作業Ji需要機器j的處理時間為tij(1<=i<=n,1<=j<=3),每個作業必須先由機器1處理,再由機器2處理,最後由機器3處理。批處理作業排程問題要求確定這n個作業的最優處理順序,使得從第1個作業在機器1上處理開始,到最優一個作業在機器3上處理結束所需的時間最少。
舉個實現的例子,以(J2,J3,J1,J4)的順序執行,總完成時間為54
另外大家思考一個問題,如果以Ji開始處理,估計處理的最短時間為:
這個公式其實很容易理解,機器1開始工作,需要ti1的時間,然後機器2才能處理J1,這時候機器2不空閒,時間最短,當所有任務在機器2完成後,最後完成的任務,在機器3上執行。所以最短時間由這三段組成。
大家如果瞭解了這個,那麼目標函式值就很容易理解了。
對於一個已安排的作業集合M是{1,2,……,n}的子集,|M|=k,即已安排了k個作業,設sum1[k]表示機器1完成k個作業的處理時間,sum2[k]表示機器2完成k個作業的處理時間,現在要處理作業k+1,此時,該部分解的目標函式值的下界計算方法如下:
分支限界法的搜尋過程為:
總結
分支限界法也是有套路,先確認上下界,然後找出合理的限界函式,最後就是使用廣度優先遍歷,按照限界函式計算出的數值進行排序,每次選取最小/最大值進行下一次的廣度優先遍歷。
樂扣裡的題目很少有需要使用到限界函式的,不過有一些題需要更多的技巧。本章中的題大家可以聯絡一下,中等難度的題就問題不大了。
最後
大家如果喜歡我的文章,可以關注我的公眾號(程式設計師麻辣燙)
我的個人部落格為:shidawuhen.github.io/
往期文章回顧:
演算法
技術
- 微服務之服務框架和註冊中心
- Beego框架使用
- 淺談微服務
- TCP效能優化
- 限流實現1
- Redis實現分散式鎖
- Golang原始碼BUG追查
- 事務原子性、一致性、永續性的實現原理
- CDN請求過程詳解
- 記部落格服務被壓垮的歷程
- 常用快取技巧
- 如何高效對接第三方支付
- Gin框架簡潔版
- InnoDB鎖與事務簡析
讀書筆記
思考