详见笔者博文:二分搜索的那些事儿,很是全面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; // 存在没有可行路径的可能 }