遞歸、回溯-算法框架


之前已經學習過回溯法的一些問題,從這篇文章開始,繼續深入學習一下回溯法以及其他經典問題。

回溯法有通用的解題法之稱。用它可以系統的搜索一個問題的所有解或任一解,回溯法是一個既帶有系統性又帶有跳躍性的搜索算法。

它的問題的解空間樹中,按深度優先策略,從根結點出發搜索解空間樹。算法搜索至解空間樹的任一結點時,先判斷該結點是否包含問題的解。如果肯定不包含,則跳過對以該結點為根的子樹的搜索,逐層向其祖先結點回溯。否則,進入該子樹,繼續按深度優先策略搜索。回溯法求問題的所有解時,要回溯到根,且根結點的所有子樹都已被搜索遍才結束。回溯法求問題的一個解時,只要搜索到問題的一個解就可結束。

這種以深度優先方式搜索問題解的算法稱為回溯法,它適用於解組合數較大的問題。

回溯法的算法框架:

1、問題的解空間

用回溯法解問題時,應明確定義問題的解空間。問題的解空間至少應包含問題的一個(最優解)。

2、回溯法的基本思想

確定了解空間的組織結構后,回溯法從開始結點出發,以深度優先方式搜索整個解空間。這個開始結點稱為活結點,同時也稱為當期那的擴展結點,如果在當前的擴展結點處不能再向縱深方向移動,則當前擴展結點就稱為死結點。此時,應往回移動(回溯)至最近的一個或活結點處,並使這個活結點稱為當前的擴展結點。回溯法以這種工作方式遞歸地在解空間中搜索,直至找到所要求的解或解空間中已無活結點時為止。

3、遞歸回溯 

回溯法對解空間作深度優先搜索,因此在一般情況下可用遞歸函數來實現回溯法如下:

void Backtrack(int t)
{
    if(t > n)                             //t>n時已搜索到一個葉結點,output(x)得到的可行解x進行記錄或輸出處理
        Output(x);
    else                                  //當前拓展結點是解空間樹的內部結點
    {
        for(int i = f(n,t); i <= g(n, t); i++)   //函數f和g分別表示當前擴展結點處未搜索子樹的起止編號
        {
            x[t] = h(i);                         //h(i)表示在當前擴展結點處x[t]的第i個可選值
            if(Constraint(t) && Bound(t))
                Backtrack(t+1);
        }                                        //循環結束時,已搜索遍當前擴展結點的所有未搜索子樹
    }
}  

其中,形式參數t表示遞歸深度,即當前擴展結點在解空間樹中的深度。n用來控制遞歸深度,當t>n時,算法已搜索到葉結點,此時,由Output(x)記錄或輸出得到的可行解x。算法BackTrack的for循環中f(n,t)和g(n,t)分別表示在當前擴展結點處未搜索過的子樹的起始編號和終止編號。h(i)表示在當前擴展結點處x[t]的第i個可選值。Constraint(t)和Bound(t)表示在當前擴展結點處的約束函數和限界函數。Constraint(t)返回的值為true時,在當前擴展結點處x[1:t]的取值滿足問題的約束條件,否則不滿足問題的約束條件,可剪去相應的子樹。

Bound(t)返回的值為true時,在當前擴展結點處x[1:t]的取值未使目標函數越界,還需由Backtrack(t+1)對其相應的子樹做進一步搜索。

否則,當前擴展結點處x[1:t]的取值使目標函數越界,可剪去相應的子樹。執行了算法的for循環后,已搜索遍當前擴展結點的所有未搜索過的子樹。Backtrack(t)執行完畢,返回t-1層繼續執行,對還沒有測試過的x[t-1]的值繼續搜索。當t=1時,若已測試完x[1]的所有可選值,外層調用就全部結束。顯然,這一搜索過程按深度優先方式進行,調用一次Backtrack(1)即可完成整個回溯搜索過程。

4、迭代回溯

采用樹的非遞歸深度優先遍歷算法,也可將回溯法表示為一個非遞歸的迭代過程如下:

void IterativeBacktrack()
{
    int t;
 
    t = 1;                                       //當前擴展結點在解空間樹中的深度,在這一層確定解向量的第t個分量x[t]的取值
    while(t > 0)
    {
        if(f(n,t) <= g(n,t))                    //f和g分別表示在當前擴展結點處未搜索子樹的起止編號
        {
            for(int i = f(n,t); i <= g(n,t); i++)
            {
                x[t] = h(i);                    //h(i)表示在當前擴展結點處x[t]的第i個可選值
                if(Constraint(t) && Bound(t))
                {
                    if(Solution(t))             //solution(t)判斷當前擴展結點處是否已得到問題的一個可行解
                        Output(x);
                    else
                        t++;                    //solution(t)為假,則僅得到一個部分解,需繼續縱深搜索
                }
            }
        }
        else
            t--;                                //如果f(n,t)>g(n,t),已搜索遍當前擴展結點的所有未搜索子樹,
    }                                           //返回t-1層繼續執行,對未測試過的x[t-1]的值繼續搜索
}       

上述迭代回溯算法中,用Solution(t)判斷在當前擴展結點處是否已得到問題的可行解。它返回的值為true時,在當前擴展結點處x[1:t]是問題的可行解。此時,由Output(x)記錄或輸出得到的可行解。它返回的值為false時,在當前擴展結點處x[1:t]只是問題的部分解,還需向縱深方向繼續搜索。

算法中f(n,t)和g(n,t)分別表示在當前擴展結點處未搜索過的子樹的起始編號和終止編號。h(i)表示在當前擴展結點處x[t]的第i個可選值。Constraint(t)和Bound(t)是當前擴展結點處的約束函數和限界函數。Constraint(t)的返回的值為true時,在當前擴展結點處x[1:t]的取值滿足問題的約束條件,否則不滿足問題的約束條件,可剪去相應的子樹。Bound(t)返回的值為true時,在當前擴展結點處x[1:t]的取值未使目標函數越界,還需對其相應的子樹做進一步搜索。否則,當前擴展結點處x[1:t]的取值已使目標函數越界,可剪去相應的子樹。算法的while循環結束后,完成整個回溯搜索過程。

5、字集樹與排列樹

當所給的問題是從n個元素的集合S中找出滿足某種性質的子集時,相應的解空間數稱為子集樹。這類子集樹通常有個葉結點,其結點總個數為.遍歷子集樹的任何算法均需的計算時間。

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

當所給的問題是確定n個元素滿足某種性質的排列時,相應的解空間樹稱為排列樹。排列樹通常有n!個葉結點。因此遍歷排列數需要\Omega (n!)的計算時間。

void Backtrack(int t)
{
    if(t > n)
        Output(x);
    else
    {
        for(int i = t; i <= n; i++)
        {
            swap(x[t], x[i]);
            if(Constraint(t) && Bound(t))
                Backtrack(t+1);
            swap(x[t], x[i]);
        }
    }
}

在調用Backtrack(1)執行回溯搜索之前,先將變量數組x初始化為單位排列(1,2,....,n)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM