1. 程式人生 > >淺談回溯算法

淺談回溯算法

nat con style mes 包括 問題: exist 結點 深度優先

1.定義: 回溯算法是一種在窮舉查找基礎上的增強變形。主要是在嘗試搜索的過程中,每次只構造解的一個分量,當發現部分構造解滿足求解條件時,就接受下一個分量所做的第一個合法選擇;當發現部分構造解不滿足求解條件時,就回溯返回,嘗試另外的路徑。這種走不通就回頭的算法稱為回溯算法。 主要思想:通過對所做的選擇構造一顆狀態空間樹,按照深度優先的策略,從根開始深度搜索狀態空間樹。當搜索到某一結點,先判斷該節點是否可以構成完整解,如果可以就繼續向下搜索;否則就逐層返回回溯,嘗試其他路徑。(隱式圖的深度優先搜索) 核心思想:個人以為,回溯法的核心是狀態空間樹的構造(可能是隱式構造的),一旦確立了狀態空間樹,就可以直接得出問題的解。狀態空間樹的構造思想是:按照深度優先的方式,如果當前節點的下一個分量所構造的部分解可以導致完整解,就生成節點的子女,並添加下一個分量的第一個合法選擇;否則就回溯到該節點的父母,考慮部分解的下一個分量選擇。 2.回溯算法的一般步驟(解決某些約束下的最優解):
(1)確定問題的解空間(包含所有解的一顆狀態空間樹),確定完全解的形式,以及部分構造解無法構成完全解的剪枝規則。( ps:問題的解空間(狀態空間樹)是在DFS的過程中動態生成的) (2)確定節點(分量)的擴展規則,即下一個節點的選擇規則。 (ps:如曼哈頓問題中,按照字母順序選擇下一個分量) (3)以深度優先的方式搜索解空間,並在搜索的過程中用剪枝函數判斷該節點是否可以生成完全解。如果可以,則進入該節點的子樹下一步搜索(構造下一個分量);如果不能,則跳過該節點的子樹(不再生成下一個分量),逐層回溯。(ps:剪枝函數包括約束函數和界限函數,分別剪去不滿足約束的節點和不能得到最優解的函數
3.回溯的算法框架: (1)遞歸的方式:
int x[n];
void backtrack(int t)
{
    if(t>n)              //到達葉子節點,輸出結果,x是可行解
        output(x);
    else
    {
        for i = 1 to k   //該節點的子節點(分量的所有下一個分量)
        {
            x[t] = value(i);    //取出子節點的值
            if(constraint(t) && bount(t) )    //
剪枝函數:判斷約束和界限 backtrack(t+1); //可以生成完全解,繼續遞歸下去 } } }
特點:思路簡單,設計簡單,但算法時間效率很差。 (2)遞推的方式:
void backtrack()
{
    int t=1;
    while(t>0)
    {
        if(existSubNode(t))    //存在子節點:該結點還有可以構造的節點(下一個分量)
        {
            for i = 1 to k
            {
                x[t] = value(i);    //相當於在此處建立一個結點
                if(constraint(t) && bount(t) )    //剪枝函數判斷約束和界限
                {
                    if(isResult(t) )    //得到了一個結果,輸出
                        output(x);
                    else                //還沒有得到結果,繼續向下搜索
                        t++;
                }
                else
                {
                    eraseSubNode(t)    //該結點無法構成完全解,故刪去該結點,並設該結點不可再作為子節點。
                }
            }
        }
        else
        {
            eraseSubNode(t);    //該結點沒有子結點,也不能完全解,所以刪去該結點。
            t--;                //進行回溯
        }    
    }
}
特點:設計復雜,但算法時間效率很高。 4.用回溯的思想實現背包問題: 用回溯算法解決01背包問題,步驟: (1)問題的解空間是子集樹,節點表示前 t 個物品的存放狀態,樹枝的值表示第 t 個物品有沒有放入背包。完全解的形式是01組成的N個數,避免無法構造完全解的約束剪枝規則是:當前背包的物品重量CurWeight加新增的物品重量不能超過背包的承重C(CurWeight+w[t]<C)。 當物品數為3時,解空間(狀態空間樹)如下:         技術分享圖片 (2)節點擴展規則:數字遞增順序,也就是說第t個物品放入背包和沒有放入背包的狀態,即 0 和 1. (3)回溯搜索:如果搜索到葉子節點,表示一條路徑搜索結束,如果存在更優解則記錄。 如果沒有搜索到葉子節點,則遍歷其子節點,滿足剪枝條件時繼續向下搜索,不滿足時回溯。
#include <iostream>
using namespace std;
#define N  4                     //物品總數
#define C  5                     //背包的承重
int w[N] = {2,1,3,2};           //物品重量
int v[N] = {12,10,20,15};     //物品價值
int CurWeight = 0;            //當前總重量
int CurValue = 0;               //當前總價值
int x[N] ={0,0,0};                 //當前的背包選取情況(1表示選取,0表示不選取)
int MaxValue = 0;              //最大價值
int MaxPack[N] = {0,0,0};     //最大價值下的背包選取情況(1表示選取,0表示不選取)
void backtrack(int t)
{
    if(t >= N)    //到達葉子處,說明已經求得一個滿足約束的完全解,接下來判斷是否最優
    {
        if(CurValue > MaxValue)
        {
            MaxValue = CurValue;
            for(int i=0;i<N;i++)
                MaxPack[i] = x[i];
        }
    }
    else    //沒到葉子處,還要繼續向下搜索下去
    {
        for(int i=0;i<=1;i++)
        {
            x[t] = i;            //01背包問題中,每個物品的存放狀態是 0 或 1。此處表示第 t 個物品在背包中的狀態為 0 或 1。
            if(x[t] == 0)     //第 t 個物品沒有放入背包,不進行剪枝判斷
            {
                backtrack(t+1);     
            }
            else            //第 t 個物品放入背包,要進行剪枝判斷
            {
                if(CurWeight + w[t] <= C)    //剪枝判斷為滿足約束條件
                {
                    CurWeight += w[t];   //當前結點滿足約束,則增加結點的值到當前值中
                    CurValue += v[t];
                    backtrack(t+1);          //繼續向下搜索
                    CurWeight -= w[t];    //為了回溯到父節點的狀態,要將子節點新增的內容刪掉
                    CurValue -= v[t];
                }    
            }
        }
    }
}

int main()
{
    backtrack(0);
    cout<<"the max value pack combination: ";
    for(int i=0; i<N; i++)
        cout<<MaxPack[i]<<" ";
    cout<<"\nthe max value: "<<MaxValue<<endl;
}

淺談回溯算法