詳見筆者博文:二分搜索的那些事兒,很是全面html
(1) 矩陣每行遞增,且下一行第一個元素大於上一個最後一個元素node
(2) 矩陣每行遞增,且每列也遞增c++
適用場景:數組
(1) 輸入數據:若是是 遞歸數據結構(如單鏈表、二叉樹),則必定可使用DFS緩存
(2) 求解目標:必須走到最深處(例如二叉樹,必須走到葉子節點)才能獲得一個解,這種狀況通常適合用DFS數據結構
思考步驟:函數
(1) DFS最多見的3個問題:求可行解的總數、求任一個可行解、求全部可行解spa
(a) 若是是 求可行解總數,則不須要 數組path[] 來存儲 搜索路徑指針
(b) 若是是 求可行解自己,則須要一個 數組path[] 來存儲 搜索路徑序列c++11
DFS在搜索過程當中 始終只有一條搜索路徑,一直搜索到絕境再回溯繼續搜索,所以只須要一個數組就能夠了
BFS須要存儲 擴展過程當中的搜索路徑,在沒有找到答案以前 全部路徑都不能放棄
(2) 只要求任一可行解? 要求全部可行解?
若是隻須要一個可行解,找到一個便可返回
若是要求全部可行解,找到一個可行解以後,必須繼續擴展,直到遍歷完
BFS通常只要求一個解,若是用BFS要求全部解,就須要擴展到全部葉子節點,至關於在內存中有指數級的存儲空間
(3) 如何表示狀態?
一個狀態須要存儲哪些必要的信息,纔可以正確的擴展到下一步狀態.
DFS通常使用函數參數的方法,由於DFS通常有遞歸操做,擴展下一狀態只須要修改 遞歸函數的函數參數便可
BFS通常使用struct結構體存儲全部信息,struct裏的字段與DFS中的函數參數字段一一對應
(4) 如何擴展狀態?
對於二叉樹:擴展左子樹、右子樹
對於圖、矩陣:題目告知,好比 只能向右或向下走, 好比 上下左右四個方向都可擴展
(5) 如何判重?
(a) 是否須要判重?
若是 狀態轉換圖是 一棵樹,則不須要判重,樹的全部子樹均分離,不存在重疊子問題,所以二叉樹的全部DFS都不須要判重
若是 狀態轉換圖是 DAG(有向無環圖),則須要判重,所以 全部的BFS都須要判重
(b) 怎樣判重?
(6) 搜索的終止條件是什麼?
終止條件是 不能繼續擴展的末端結點
對於樹:葉子節點
對於圖:出度爲0的節點
(7) 收斂條件是什麼?
爲了判斷是否到達收斂條件,DFS通常須要在遞歸函數接口裏 用一個參數記錄當前狀態(cur變量) 或者 距離目標還有多遠(gap變量)
若是是 求一個解,直接返回這個解,即path路徑數組
若是是 求全部解,則把 這個解path數組複製到 解集合中 (通常利用 c++ vector中的push_back函數,push時採用的是copy構造函數)
(8) 如何加速?
(a) 剪枝:圖的DFS中須要挖掘各類信息,包括搜索邊界、值大小關係等
(b) 緩存:狀態轉換圖是DAG ==> 存在重疊子問題 ==> 字問題的解會被重複利用
若是輸入結構是 二叉樹,不存在重疊子問題,不須要緩存
通常使用c++11的 std::unordered_map來緩存,或者使用一個二維數組 std::vector<std::vector<int>>
DFS模板:
數據結構爲樹(二叉樹)的DFS模板(不須要判重和緩存):
/** DFS模板 * @param[in] input :對於二叉樹通常爲root指針,對於圖通常是輸入矩陣即二維數組 * @param[in] path:當前搜索路徑,也是中間結果,通常爲一維vector * @param[in] cur or gap:標記當前位置或距離目標的距離 * @param[out] result:存放最終結果,通常是二維vector,每一維爲path數組 */ void dfs(type input, std::vector<int>& path, int cur or gap, std::vector<std::vector<int>>& result) { if (數據非法) return; // 終止條件,對於二叉樹即input爲空,對於圖即 搜索邊界越界 if (cur == input.size() or gap == 0) { // 收斂條件 result.push_back(path); } // 執行全部擴展路徑 path.push_back(); // 執行動做,修改path // 擴展動做通常有多個,對於二叉樹就是 input->left和input->right,對於圖可能就是 y-1,y+1,x-1,x+1(上下左右) dfs(input, path, cur + 1 or gap - 1, result); path.pop_back(); // 恢復path }
例題: 二叉樹路徑和問題
數據結構爲圖的DFS模板(須要判重):
/** DFS模板 * @param[in] input :對於二叉樹通常爲root指針,對於圖通常是輸入矩陣即二維數組 * @param[in] path:當前搜索路徑,也是中間結果,通常爲一維vector * @param[in] cur or gap:標記當前位置或距離目標的距離 * @param[in] visited:用於判重的二維數組 * @param[out] result:存放最終結果,通常是二維vector,每一維爲path數組 */ void dfs(type input, std::vector<int>& path, int cur or gap, std::vector<std::vector<bool>> visited, std::vector<std::vector<int>>& result) { if (數據非法) return; // 終止條件,對於二叉樹即input爲空,對於圖即 搜索邊界越界 if (cur == input.size() or gap == 0) { // 收斂條件 result.push_back(path); } if(visited[x][y] == true) return; // 判重 // 執行全部擴展路徑 visited[x][y] = true; path.push_back(); // 執行動做,修改path // 擴展動做通常有多個,對於二叉樹就是 input->left和input->right,對於圖就是 y-1,y+1,x-1,x+1(上下左右) dfs(input, path, cur + 1 or gap - 1, visited, result); path.pop(); // 恢復path visited[x][y] = false; }
例題:在字符矩陣中查找單詞
適用場景:求最短搜索路徑
/** BFS模板 * param[in] state_t:狀態,如整數、字符串、數組等 * param[in] start:起點 * parma[in] grid:輸入矩陣數據 * return 從起點到目標狀態的一條最短路徑 */ std::vector<state_t> bfs(state_t start, std::vector<std::vector<int>>& grid) { std::queue<state_t> que; // 隊列 std::unordered_set<state_t> visited; // 判重,也能夠直接使用 二維vector bool found = false; que.push(start); visited.insert(start); while (!que.empty()) { state_t state = que.front(); que.pop(); if (state爲目標狀態) { found = true; break; } // 擴展狀態,對於二叉樹即左右子樹,對於圖可能就是上下左右四個方向 std::vector<state_t> stateVec = state_extend(state); // 擴展狀態必須考慮 搜索邊界越界時的剪枝 和 visited的判重 for (auto iter = stateVec.begin(); iter != stateVec.end(); ++iter) { state_t curState = *iter; if (curState爲目標狀態) { found = true; break; } // curState不知足目標狀態,則入隊 que.push(curState); visited.insert(start); } } if (found) { return generate vector<state_t>; } else { return vector<state_t>(); } }
走迷宮問題
迷宮矩陣中元素全爲0或1,0表明通路,1表明障礙,每次能夠上下左右四個方向走,如今要求:
求 出發點到終點的全部可行路徑?
求 出發點到終點的最短路徑?
前面講過,最短問題通常使用BFS,可不可使用DFS呢? 固然能夠
記住:DFS能夠求出出全部的可行解,全部的解都求出來了,最短的路徑固然能夠肯定了,只是這是DFS的效率明顯比BFS低
如今咱們使用 DFS 和 BFS 求第二個問題
(1) DFS求解,每次求得一個可行路徑時,咱們就與前一個可行解作出大小判斷,最後結果即爲最短路徑
int dx[4] = {-1, 1, 0, 0}; // x --> row int dy[4] = {0, 0, -1, 1}; // y --> col int minStep = INT_MAX; // 最終結果 void dfs(std::vector<std::vector<int>>& maze, const std::vector<int>& start, const std::vector<int>& end, int curX, int curY, int step) { if (curX == end.at(0) && curY == end.at(1)) { // 求得可行解 minStep = std::min(minStep, step); // 更新最小解 return; } for (int i = 0; i < 4; ++i) { // 上下左右4個方向進行搜素擴展 int nextX = curX + dx[i]; int nextY = curY + dy[i]; if (nextX < 0 || nextX >= maze.size()) continue; // 搜索邊界剪枝 if (nextY < 0 || nextY >= maze.at(0).size()) continue; // 搜索邊界剪枝 if (maze.at(nextX).at(nextY) == 0) { // 必須是通路,即判重 maze.at(nextX).at(nextY) = 1; // 標記已訪問過 dfs(maze, start, end, nextX, nextY, step + 1); // 執行搜索擴展 maze.at(nextX).at(nextY) = 0; // 回溯操做 } } }
(2) BFS求解,使用一個結構體保存座標狀態,使用一個隊列輔助BFS操做
int dx[4] = {-1, 1, 0, 0}; // x --> row int dy[4] = {0, 0, -1, 1}; // y --> col int minStep = 0; // 最終結果 // 保存座標狀態 struct node { int x; int y; node(int xPos, int yPos) : x(xPos), y(yPos) { } }; std::queue<node> que; int minStep(std::vector<std::vector<int>>& maze, const std::vector<int>& start, const std::vector<int>& end) { que.push(node(start.at(0), start.at(1))); maze.at(start.at(0)).at(start.at(1)) = 1; //標記已訪問過 while (!que.empty()) { node curNode = que.front(); que.pop(); if (curNode.x == end.at(0) && curNode.y == end.at(1)) { // 求得可行解,BFS必定是最短 return minStep; } for (int i = 0; i < 4; ++i) { // 上下左右4個方向進行搜索擴展 int nextX = curNode.x + dx[i]; int nextY = curNode.y + dy[i]; if (nextX >= 0 && nextX < maze.size() && nextY >= 0 && nextY < maze.at(0).size() && maze.at(nextX).at(nextY) == 0) { que.push(node(nextX, nextY)); maze.at(nextX).at(nextY) = 1; // 標記已訪問過 } } ++minStep; // 增大每一層搜索的距離 } return 0; // 存在沒有可行路徑的可能 }