算法——回溯

今天不說廢話了,回溯法之前好像沒有聽說過,直接進入主題

參考:https://blog.csdn.net/JarvisChu/article/details/16067319

概念

回溯法(探索與回溯法)是一種選優搜索法,又稱爲試探法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術爲回溯法,而滿足回溯條件的某個狀態的點稱爲“回溯點”。

(百度百科)

將問題的解空間轉化爲樹或者圖的結構進行表示,然後使用深度優先算法進行遍歷,遍歷中記錄最優解。

與遞歸的區別:在考慮計算所有可能性的題目中,經常使用遞歸算法,得出所有解,遞歸並不在每一步進行判斷是否爲最優解,而進行捨去。而回溯法是需要進行判斷的是否爲最優或者能否達到最優的。

基本思想

回溯法從根節點出發,按照深度優先策略遍歷解空間樹,搜索滿足條件的解。

在回溯法中,每次擴大當前部分解時,都面臨一個可選的狀態集合,新的部分解就通過在該集合中選擇構造而成。這樣的狀態集合,其結構是一棵多叉樹,每個樹結點代表一個可能的部分解,它的兒子是在它的基礎上生成的其他部分解。樹根爲初始狀態,這樣的狀態集合稱爲狀態空間樹。

初始時,根節點是活節點,也稱爲當前擴展節點。在當前擴展節點處,搜索向縱深方向移至另一新節點,使新節點成爲活結點以及當前擴展節點。當算法移至某一新節點時,使用剪枝函數判斷當前節點是否可行。若不可行則跳過該節點,直接回溯,否則進入子樹繼續搜索。當前節點不能再向縱深方向移動時,當前節點就成爲了死節點。此時,往回移動至最近活節點,並使之成爲當前擴展節點。

剪枝函數包括兩類,使用約束條件減去不滿足條件的路徑、使用限界函數減去不滿足最優的路徑。

回溯法的關鍵在於如何定義解空間樹,解空間樹分爲兩種:

子集樹:所給問題是從n個元素的集合中找到滿足某種性質的子集時,解空間爲子集樹。

排列樹:所給問題是從n個元素的集合中找到滿足某種性質的排列時,解空間爲排列樹。

求解步驟

1)定義問題的解空間;

2)確定易於搜索的解空間結構;

3)以深度優先策略搜索解空間,並在搜索過程中用剪枝函數避免無效搜索;

例子

裝載問題

問題描述:

有n個集裝箱要裝上2艘載重量分別爲c1和c2的輪船,其中集裝箱i的重量爲wi,且∑wi <= c1 + c2。

問是否有一個合理的裝載方案,可將這n個集裝箱裝上這2艘輪船。如果有,找出一種裝載方案。

分析:

所謂最優裝載方案爲:(1)首先將第一艘輪船儘可能裝滿;(2)將剩餘的集裝箱裝上第二艘輪船。

看到這個題怎麼想得到用回溯法呢?如何使用解空間樹?

問題的解是得到一個子集,因此使用子集樹作爲解空間。確定約束條件和限界函數

子集樹的構建:使用怎樣的方式進行子集樹的建立?每一個物品都需要進行決定是否需要,可以建立n層數結構。

參考:https://blog.csdn.net/m0_38015368/article/details/80196634

代碼實現:

n  集裝箱數; w[]集裝箱重量數組; c第一艘輪船載重量;
cw  在遍歷結點處的當前載重量   bsetw 當前最優載重量

void backtrack (int i) {
    if (i > n){
        if(wc > wm) wm = wc; return;
    }
    wr -= w[i];
    if (wc + w[i] <= c){  // x[i] = 1; 搜索左子樹
        wc += w[i];
        backtrack(i+1);
        wc -= w[i];
    }
    if (wc + wr > wm){  // x[i] = 0; 搜索右子樹
        backtrack(i+1);
    }
    wr += w[i];
}

 

N皇后問題

問題描述:在n×n格的國際象棋上擺放n個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。

n皇后是由八皇后問題演變而來的。該問題是國際西洋棋棋手馬克斯·貝瑟爾於1848年提出:在8×8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。 高斯認爲有76種方案。1854年在柏林的象棋雜誌上不同的作者發表了40種不同的解,後來有人用圖論的方法解出92種結果。
 

分析:

•    依次在棋盤的每一行上擺放一個皇后。
•    每次擺放都要檢查當前擺放是否可行。如果當前的擺放引發衝突,則把當前皇后擺放到當前行的下一列上,並重新檢查衝突。
•    如果當前皇后在當前行的每一列上都不可擺放,則回溯到上一個皇后並且將其擺放到下一列上,並重新檢查衝突。
•    如果所有皇后都被擺放成功,則表明成功找到一個解,記錄下該解並且回溯到上一個皇后。

解空間爲排列樹。約束條件爲不在同列,同斜線

並不需要一個n*n的數組,我們只需要一個n長度的數組來存位置。

arr[i] = k; 表示: 第i行的第k個位置放一個皇后。這樣一個arr[n]的數組就可以表示一個可行解, 由於回溯,我們就可以求所有解。

參考:https://blog.csdn.net/qq908821304/article/details/80903114

代碼實現:

bool Bound(int k){
    for (int i = 1; i < k; i++){
        if ((abs(k-i)==abs(x[i]-x[k]))||(x[i]==x[k])) 
            return false;
    }
    return true;
} 

void Backtrack(int t){
    if (t>n) output(x);
    else {
        for (int i = 1; i <= n; i++) {
            x[t] = i;
            if (Bound(t)) Backtrack(t+1);
        }
    }
 }

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章