回溯法是一種選優搜索法(試探法),被稱爲通用的解題方法,這種方法適用於解一些組合數至關大的問題。經過剪枝(約束+限界)能夠大幅減小解決問題的計算量(搜索量)。html
將n元問題P的狀態空間E表示成一棵高爲n的帶權有序樹T,把在E中求問題P的解轉化爲在T中搜索問題P的解。ios
深度優先搜索(Depth-First-Search,DFS)是一種用於遍歷或搜索樹或圖的算法。沿着樹的深度遍歷樹的節點,儘量深的搜索樹的分支。當節點v的所在邊都己被探尋過,搜索將回溯到發現節點v的那條邊的起始節點。這一過程一直進行到已發現從源節點可達的全部節點爲止。若是還存在未被發現的節點,則選擇其中一個做爲源節點並重復以上過程,整個進程反覆進行直到全部節點都被訪問爲止。 --from wiki算法
一、按選優條件對T進行深度優先搜索,以達到目標。數組
二、從根結點出發深度優先搜索解空間樹函數
三、當探索到某一結點時,要先判斷該結點是否包含問題的解spa
四、算法結束條件.net
當問題是:從n個元素的集合S中找出知足某種性質的子集時相應的解空間樹稱爲子集樹,例如n個物品的0/1揹包問題。指針
當問題是:肯定n個元素知足某種性質的排列時相應的解空間樹稱爲排列樹,例如旅行商問題。code
DFS搜索在程序中能夠兩種方式來實現,分別是非遞歸方式和遞歸方式。前者思路更加清晰,便於理解,後者代碼更加簡潔高效。htm
非遞歸實現須要藉助堆棧(先入後出,後入先出),在C++中使用stack容器便可。
若給定一個序列,須要找到其中的一個子序列,判斷是否知足必定的條件。下面將程序實現DFS對子序列的搜索過程。
一、首先將根節點放入堆棧中。
二、從堆棧中取出第一個節點,並檢驗它是否爲目標。
三、重複步驟2。
四、若是不存在未檢測過的直接子節點。
五、重複步驟4。
六、若堆棧爲空,表示整張圖都檢查過了——亦即圖中沒有欲搜尋的目標。結束搜尋並回傳「找不到目標」。
/********************************************************************* * * Ran Chen <wychencr@163.com> * * Back-track algorithm (by DFS) * *********************************************************************/ #include <iostream> #include <vector> #include <stack> using namespace std; class Node { public: int num; // 節點中元素個數 int sum; // 節點中元素和 int rank; // 搜索樹的層級 int flag; // 0表示子節點都沒訪問過,1表示訪問過左節點,2表示訪問過左右節點 vector <int> path; // 節點元素 Node(); Node(const Node & nd); }; // 默認構造函數 Node::Node() { num = 0; sum = 0; rank = 0; flag = 0; // path is empty } // 複製構造函數 Node::Node(const Node & nd) { num = nd.num; sum = nd.sum; rank = nd.rank; flag = nd.flag; path = nd.path; } // ----------------------------------------------------------------- void DFS(const vector <int> & deque) { stack <Node *> stk; // 存儲節點對應的指針 stack <Node *> pre_stk; // 存儲上一級節點(回溯隊列) Node * now = new Node; // 指向當前節點 Node * next = NULL; // 指向下一個節點 Node * previous = NULL; // 指向上一個節點 while (now) { if (now->rank < deque.size() && (now->flag == 0)) { // 左葉子節點,選擇當前rank的數字 next = new Node(*now); next->num++; next->sum += deque[next->rank]; next->path.push_back(deque[next->rank]); next->rank++; next->flag = 0; stk.push(next); // 將左節點加入堆棧中 now->flag = 1; // 改變標誌位 // 將當前節點做爲上一級節點存儲並刪除 previous = new Node(*now); pre_stk.push(previous); delete (now); // 取出堆棧中的待選節點做爲當前節點 now = stk.top(); stk.pop(); // 顯示搜索路徑 for (int i = 0; i < next->path.size(); ++i) { cout << " " << next->path[i] << " "; } cout << endl; continue; // DFS每次僅選取一個子節點,再進入下一步循環 } if (now->rank < deque.size() && (now->flag == 1)) { // 右節點,不選擇當前rank的數字 next = new Node(*now); next->rank++; next->flag = 0; stk.push(next); now->flag = 2; // 將當前節點做爲上一級節點存儲並刪除 previous = new Node(*now); pre_stk.push(previous); delete (now); // 取出堆棧中的待選節點做爲當前節點 now = stk.top(); stk.pop(); continue; } // 回溯結束 if (pre_stk.empty()) { break; } // 沒有子節點或者沒有未搜索過的子節點時,回退到上一級節點(回溯) if (now->rank >= deque.size() || now->flag == 2) { delete (now); now = pre_stk.top(); pre_stk.pop(); } } } // ----------------------------------------------------------------- int main() { stack <Node*> stk; vector <int> deque { 2,3,5,7 }; DFS(deque); cin.get(); return 0; }
一、定義了一個Node節點類,表示當前狀態下已經搜索到的序列,path記錄了這個子序列的值,而且類中添加了num(子序列中元素數目)、sum(子序列元素和)等屬性,經過這些屬性能夠判斷是否找到滿意解或者用於剪枝。
二、對於原始序列中某個位置的數,其子序列中能夠包含這個數,也能夠不包含這個數,因此每次有兩種選擇,即每一個節點有兩個子節點。
三、flag屬性標識了當前節點的子節點遍歷狀況。若flag=0,表示子節點都沒訪問過,下一步優先訪問左節點,因此將左節點加入堆棧中;flag=1,表示訪問過左節點,下一步訪問右節點;flag=2,表示訪問過左右節點。
四、當沒有子節點(now->rank >= deque.size())或者左右節點都訪問過期(flag=2),回溯到上一級節點。
五、程序循環中,首先經過now當前節點,找到下一個子節點next,將其加入堆棧中,便於下一步循環。在now節點銷燬前,將其存到previous,並加入pre_stk堆棧中。這樣在下一輪循環中,previous相對於now就是上一級節點,若是now不能找到其子節點,就要返回上一級,這樣previous就能夠從新賦給now,達到返回上一級的目的。
六、整個程序的終止條件是pre_stk堆棧爲空時截止,說明全部節點都已經遍歷過,而且沒有再可回溯的節點了。實際運用中,能夠經過其餘屬性(搜索到可行解)來提早終止程序。
#include<cstdio> #include <iostream> int n, k; __int64 sum = 0; int a[4] = { 2, 3, 5, 7 }, vis[4] = {0, 0, 0, 0}; void DFS(int i, int cnt, int sm)//i爲數組元素下標,sm爲cnt個數字的乘積 { if (cnt == k) // 解中已包含k個數字 { sum = sum + sm; return; } if (i >= n) return; if (!vis[i]) { // 對第i個數字進行訪問 vis[i] = 1; //a[i]被選,優先選擇第i個加入到解中,接下來搜索第i+1個數字 DFS(i + 1, cnt + 1, sm*a[i]); //a[i]不選,不選擇第i個,至關於右節點,接下來搜素第i+1個數字 DFS(i + 1, cnt, sm); vis[i] = 0; // 回溯 } return; } int main(void) { n = 4, k = 2; DFS(0, 0, 1); printf("%I64d\n", sum); std::system("pause"); return 0; }
一、程序目的:給定n個正整數,求出這n個正整數中全部任選k個相乘後的和,這裏的數組a[4]存儲原序列,vis[4]做爲訪問標誌,k取2,結果輸出爲101,對應的序列是{2, 3}{2, 5}{2, 7}{3, 5}{3, 7}{5, 7}
。
二、對於元素a[i],每次對應兩個選擇。若選擇將a[i]加入到解中,則解中元素個數+1,乘積結果*a[i],因此下一步更新爲DFS(i + 1, cnt + 1, sm*a[i])
。若不選擇a[i],則解中的元素個數和乘積不變,下一步更新爲DFS(i + 1, cnt, sm)
。
三、回溯時要將標誌位重置。