回溯法(探索與回溯法)是一種選優搜索法,又稱爲試探法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步從新選擇,這種走不通就退回再走的技術爲回溯法。算法
以上來自百度百科。數組
就是要找出這個問題全部的解,在這些解裏面篩選出最優解。
舉個例子,0-1揹包的問題,假設咱們有A,B,C三個物品,咱們要判斷選擇裝進哪些物品。從咱們的揹包裏沒有任何東西開始,咱們按順序一個個物品來判斷是否是把它裝進去。那麼咱們就可能出現兩種想法:
想法一
對每個物品判斷是裝仍是不裝,而後畫出一個不規範的的圖:函數
在這個圖中,連線旁邊的數字:1表示裝入揹包中,0表示不裝入揹包。第一層中,咱們判斷是否放入A,放入就是1那條岔路,反之是0那條岔路,因此很直觀地,咱們就能夠獲得全部可能出現的8個狀況:spa
{(1,1,1),(1,1,0),(1,0,1),(1,0,0),(0,1,1),(0,1,0),(0,0,1),(0,0,0)}
想法二
咱們假設本身一開始要從三個裏面選一個來裝入,因此就有了:code
(可是01揹包問題並不適合用這種方法來解,因此這裏就是與上面的樹作一個對比)
如上圖則表示,咱們從A,B,C三個中先選擇一個裝入,若是第一輪選擇了A,那麼剩下就還有選擇B或者Cblog
而想法一的想法,其實就能造成一顆子集樹,它在每次選擇的時候,是在一個有限選擇集合S裏面進行選擇,01揹包中,這個S就是{0,1},而想法二能造成一顆排列樹,它在每次進行選擇的時候是在給出的選項S中做選擇,這裏是{A,B,C}。
因此回溯法的寫法,也有兩種,子集樹的寫法和排列樹的寫法,作題的時候要本身選擇更加合適的方法來解決。遞歸
每種算法均可以選擇遞歸和迭代的方式來寫,顯然這裏用迭代方式來寫是很麻煩的,能夠說也沒有人選擇這樣的方法,因此咱們都採用遞歸來寫。圖片
子集樹寫法
咱們的目的就是要遍歷子集樹上的每一個解法,爲了判斷每個解對不對,因此咱們應該是深度遍歷,因此咱們須要:t(樹的深度),數組x(每一層的結果選擇)。先寫出中心的遍歷函數:it
void backtrack(int t){ if(t>=n){ output(); //此時已到達葉子結點,用於輸出一個結果 return; } //對分叉點遍歷0,1兩種狀況 for(int i=0;i<=1;i++){ x[t]=i; //給第t層表明的選擇賦可能選擇的值 if(ok(t)) //ok是判斷目前的一個解有可行的機會 backtrack(t+1); } }
此時咱們應該注意,x數組的初始值應該都爲0。for循環
排列樹寫法
也和子集樹同樣要遍歷子集樹上的每一個解法,咱們一樣須要:t(樹的深度),數組x(每一層的結果選擇)。寫出中心的遍歷函數:
void backtrack(int t){ if(t>=n){ output(); //此時已到達葉子結點,用於輸出一個結果 return; } //從第t個單元開始進行交換排列 for(int i=t;i<n;i++){ swap(x[t],x[i]); //交換兩個排列順序,獲得了一個解 if(ok(t)) //ok是判斷目前的一個解有可行的機會 backtrack(t+1); swap(x[t],x[i]); //恢復原樣,給後面的好繼續交換判斷 } }
此時咱們應該注意,由於排列樹其實就是寫出全部可能的排列,因此x數組裏面每一個單元的內容都應該是不同的,咱們在初始化的時候就應該依次賦予可能的解。
總結比較,在子集樹的公式中,for循環中的i是可能的取值,排列樹的公式中,for循環中的i是全部可能取值的存放位置。
n皇后問題,要求在一個n行n格的棋盤中擺放n個皇后,皇后不能在同一行,同一列或者同一斜線,咱們要找出有多少個解法。這個問題,咱們在選擇x數組上,一個一維數組就足夠用,下標表示在第幾行,對應的值表示在第幾列。
子集樹方法:
假如n=4,初始化x數組中每一個取值爲0,那麼咱們的for循環中i可能有0,1,2,3四種選擇,而後就是填寫ok()函數,根據約束的條件可知,ok()函數裏應該填寫判斷
皇后不能在同一行,同一列或者同一斜線
排列樹方法:
假如n=4,初始化x數組中每一個取值爲0,1,2,3,ok函數也和子集樹同樣,咱們的for循環中i也可能有0,1,2,3四種選擇,由於是採用了交換,不出現兩個重複的值,因此和子集樹相比,走過的路要少一些。
爲了提升算法的效率,咱們能夠從ok()函數來入手:
可是這兩個條件是互相矛盾的。
這是一個掙扎的小渣渣寫的文章,因此可能會有錯誤,各位大佬多指教QAQ