在包含問題的所有解的空間樹中,按照深度優先搜尋策略,從根節點出發搜尋解空間樹。
活結點:自身已生成但其孩子結點沒有全部生成的結點
擴充套件結點:指正在產生孩子結點的結點,e結點
死結點:指其所有結點均已產生的節點
首先根節點成為活結點,同時也成為當前的擴充套件結點
在當前的擴充套件結點處,搜尋向縱深方向移至乙個新結點。這個新結點就成為新的活結點,並成為當前擴充套件結點。如果在當前擴充套件結點處不能在向縱深方向移動,則當前擴充套件結點就成為死結點。此時應往回移動(回溯)至最近的乙個活結點處,並使這個結點成為當前可擴充套件結點。
回溯法:以這種方式遞迴地在解空間中搜尋,直到找到所有要求的解或解空間中已無活結點為止。
當從狀態si搜尋到s i+1後,s i+1變為死結點,則從狀態s i+1回退到si,再從si找其他可能路徑
若用回溯法求問題的所有解,需要回溯到根節點,且根節點的所有可行子樹都已被搜尋完才結束。若用回溯法求問題的任一解,只要搜尋到問題的乙個解就可以結束。
由於採用回溯法求解時存在退回到祖先結點的過程,所以需要儲存搜尋過的結點。通常有兩種方法,
其一是用自定義棧儲存祖先結點;
其二是採用遞迴法,因為遞迴呼叫會將祖先結點儲存到系統棧中,在遞迴呼叫返回時自動回退到祖先結點。
另外,用回溯法搜尋解空間時通常採用兩種策略避免無效搜尋,以提高回溯的搜尋效率,
法一:用約束函式在擴充套件結點處減除不滿足約束條件的路徑;
法二:用限界函式減去得不到的問題解或最優解的路徑
上述兩種方法統稱剪枝函式。
1.針對對給定的問題的解空間樹,問題的解空間樹應至少包含問題的乙個解或者最優解。
2.確定結點的擴充套件搜尋規則
3.以深度優先方式搜尋解空間樹,並在搜尋過程中可以採用剪枝函式來避免無效搜尋。其中,深度優先方式可以選擇遞迴回溯或者迭代(非遞迴)回溯。
設問題的解是乙個n維向量(x1,x2,…,xn),約束條件是xi滿足某種條件,記為constraint(xi);限界函式是xi應滿足某種條件,記為bound(xi),回溯法的演算法通常分為非遞迴回溯框架和遞迴回溯框架
i:對應解空間的第i層的某個結點
int x[n]
; //x存放解向量,全域性變數
void backtrack(int n)}}
else //不存在子結點,返回上一層,即回溯
i--;
}}
回溯法是對解空間的深度優先搜尋,因為遞迴演算法中的形參具有自動回退(回溯)的能力,所有許多回溯設計的演算法都設計成遞迴演算法,比同樣的非遞迴演算法設計起來更加簡便。
其中,i為搜尋的層次(深度),通常從0或1開始,分兩種情況:
(1)解空間為子集樹
一般地,解空間為子集樹的遞迴回溯框架如下:
int x[n]
; //x存放解向量,全域性變數
void backtrack(int n)
}}
採用上述演算法框架需注意以下幾點:
(1)i從1開始呼叫上述回溯演算法框架,此時根結點為第1層,葉子結點為第n+1層,當然i也可以從0開始,這樣根結點為第0層,葉子結點為第n層,所以需要將上述**中的
if(i>n)
改為if(i>=n)
(2)在上述框架中通過for迴圈使用j列舉i的所有可能路徑,如果擴充套件路徑只有兩條,可以改為兩次遞迴呼叫(例:求解0/1揹包問題、子集和問題等)
(3)這裡回溯框架只有 i 這 乙個引數,在實際應用中可以根據情況設定多個引數。
例:在乙個含有n個整數的陣列a,所有元素均不相同,設計乙個演算法求其所有子集(冪集)
例如:a[ ]=,所有子集是{},,,,,,,
思路:採用回溯法
問題的解空間為子集樹,每個元素只有兩種擴充套件,要麼選擇,要麼不選。
採用深度優先搜尋思路,解向量為x[ ],x[i]=0表示不選擇a[i]。用i掃瞄陣列a,也就是說問題初始狀態是(i=0,x的元素均為0),目標狀態是(i=n,x為乙個解)。從狀態(i,x)可以擴充套件出兩個狀態。
(1)不選擇a[i]元素——>下乙個狀態為(i+1,x[i]=0)
(2)選擇a[i]元素——>下乙個狀態為(i+1,x[i]=i)
這裡i總是遞增的,所以不會出現狀態重複的情況。
法一:標準解向量**:
#include
#include
#define maxn 100
void dispsolution(int a[
],int n,int x[
]) //輸出乙個解")
;} void dfs(int a[
],int n,int i,int x[
]) //用回溯法求解解向量x }
void main(
); //s[0 ... n-1]為給定的字串,設定為全域性變數
int n=sizeof(a)/sizeof(a[0]);
int x[maxn]
; //解向量
memset(x,0,sizeof(x))
; //解向量初始化
printf(
"求解結果\n");
dfs(a,n,0,x)
; printf(
"\n");
}
法二:不採用標準解向量,直接求結果
#include
#include
using namespace std;
void dispsolution(vector < int > path) //輸出乙個解")
;} void dfs(int a[
],int n,int i,vector < int > path) //用回溯法求子集path }
int main(
); //s[0 ... n-1]為給定的字串,設定為全域性變數
int n=sizeof(a)/sizeof(a[0]);
vector< int > path;
printf(
"求解結果\n");
dfs(a,n,0,path)
; printf(
"\n");
}
回溯法演算法框架
回溯法 有通用解題法 之稱,可以系統的搜尋乙個問題的所有解和任一解,是乙個既帶有系統性,又帶有跳躍性的搜尋演算法。演算法基本思想 確定解空間後 從開始節點出發,以深度優先的方式搜尋整個解空間。如果當前擴充套件結點不能再向縱深方向移動,當前節點為死節點。此時,應該往回移動至最近的乙個活節點處。並是這個...
回溯法演算法框架
回溯法 有通用解題法 之稱,可以系統的搜尋乙個問題的所有解和任一解,是乙個既帶有系統性,又帶有跳躍性的搜尋演算法。演算法基本思想 確定解空間後 從開始節點出發,以深度優先的方式搜尋整個解空間。如果當前擴充套件結點不能再向縱深方向移動,當前節點為死節點。此時,應該往回移動至最近的乙個活節點處。並是這個...
回溯法的演算法框架
如下 def constraint 約束函式 return true defbound 限界函式 return true defbacktracing t,lst,temp 回溯法函式 size len lst if t size print temp else for i in range 0,s...