DFS 算法总结

这篇文章会对DFS进行一个总结,列举的题目则是从LeetCode上面选的;

适用场景:

有三个方面,分别是输入数据、状态转换图、求解目标;

输入数据:如果是递归数据结构,如单链表,二叉树,集合,则百分之百可以使用深搜;如果是非递归数据结构,比如一维数组、二维数组、字符串、图,则概率要小一些;

状态转换图:树或者图;

输入数据:必须要走到最深(比如对于树,必须要走到叶子结点)才能得到一个解,这种情况比较适合用深搜;

代码模版

/**
* DFS模版
* @param input 输入数据指针
* @param path 当前路径,也是中间结果
* @param result 存放最终结果
* @param gap 标记当前位置或距离目标的距离
*
* @return 路径长度,如果是路径本身,则不需要返回长度
*/
template <typename type>
void dfs(type & input, type & path, type & result, int cur or gap) {
if (数据非法) return 0; // 终止条件
if (cur == input.size()) { // 收敛条件 (or gap == 0)
将path放入到result中;
} if (可以剪枝) return ; for (...) { //执行所有可能的扩展动作
1.执行动作,修改path
2.dfs(input, path, result, cur + 1 or gap - 1);
3.恢复path
}
}

典型例题

大概遇见这几种题型:

  • 二叉树路径
  • 图(有向图遍历,无向图遍历,拓扑排序,最短路径问题,最小生成树问题等等)
  • 构造二叉树
  • 矩阵路径
  • 构造链表
  • 删除无效括号

二叉树路径

例题为求二叉树路径

贴上代码:

/**
* 递归将更新字符串,到达叶结点时加入到数组中
*
* @param result <#result description#>
* @param root <#root description#>
* @param t <#t description#>
*/
void binaryTreePaths(vector<string>& result, TreeNode* root, string t) {
if (!root->left && !root->right) {
result.push_back(t);
return ;
} if (root->left) binaryTreePaths(result, root->left, t + "->" + to_string(root->left->val));
if (root->right) binaryTreePaths(result, root->right, t + "->" + to_string(root->right->val)); }
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
if (!root) return result; binaryTreePaths(result, root, to_string(root->val));
return result;
}

因为这里面涉及的内容很多,所以就以拓扑排序为例,例题为选课顺序;

贴上代码:

/**
* 判断有向图是否有环
* 通过DFS找环
*
* @param matrix <#matrix description#>
* @param visited <#visited description#>
* @param idx <#idx description#>
* @param flag <#flag description#>
*
* @return <#return value description#>
*/
bool DFS(vector<unordered_set<int>> &matrix, unordered_set<int> &visited, int idx, vector<bool> &flag) {
flag[idx] = true; // 标记该结点访问过
visited.insert(idx); // 找出该结点的所有邻居结点,如果存在访问过的结点或者递归,则返回true
for (auto it = matrix[idx].begin(); it != matrix[idx].end(); ++it) {
if (visited.find(*it) != visited.end() || DFS(matrix, visited, *it, flag)) {
return true;
}
} visited.erase(idx);
return false;
}
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<unordered_set<int>> matrix(numCourses);
// 要想完成第一门课程,则先完成第二门课程(后面的是先要完成的课程)
// 构建图
for (int i = 0; i < prerequisites.size(); ++i) {
matrix[prerequisites[i].second].insert(prerequisites[i].first);
} unordered_set<int> visited; // 记录一个递归访问过的结点
vector<bool> flag(numCourses, false); // 记录是否访问过结点 /**
* 遍历所有课程,也就是结点
* 判断是否标记过结点,如果没有则进行DFS判断是否存在回路,存在回路则返回false
*/
for (int i = 0; i < numCourses; ++i) {
if (!flag[i])
// 如果递归中存在访问过的结点,则该拓扑排序是不存在的,也就无法完成课程
if (DFS(matrix, visited, i, flag))
return false;
}
return true;
}

构造二叉树

这里分为链表构造和数组构造,或是已知前中后序列,构造二叉树

这里以前序和后序构造二叉树为例,贴上代码:

/**
* 利用递归进行计算左子树和右子树
*
* @param inorder <#inorder description#>
* @param postorder <#postorder description#>
* @param inStart <#inStart description#>
* @param inEnd <#inEnd description#>
* @param postStart <#postStart description#>
* @param postEnd <#postEnd description#>
*
* @return <#return value description#>
*/
TreeNode* createTree(vector<int>& inorder, vector<int>& postorder, int inStart, int inEnd, int postStart, int postEnd) {
if (postorder.empty() || inStart > inEnd || postStart > postEnd)
return NULL;
// 后序的最后一个结点是跟结点
TreeNode * root = new TreeNode(postorder.at(postEnd));
int index;
for (int i = inStart; i <= inEnd; i++) {
if (inorder.at(i) == postorder.at(postEnd)) {
index = i;
break;
}
} // 分别定为左子树和右子树 (需要注意子树的边界问题!!!!!)
root->left = createTree(inorder, postorder, inStart, index - 1, postStart, postStart - inStart + index - 1);
root->right = createTree(inorder, postorder, index + 1, inEnd, postEnd - inEnd + index, postEnd - 1);
return root;
} TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (postorder.empty())
return NULL;
return createTree(inorder, postorder, 0, (int)inorder.size() - 1, 0, (int)postorder.size() - 1);
}

矩阵路径

以Longest Increasing Path in a Matrix为例

贴上代码:

/**
* 方法和上面类似,不过利用dirs+循环可以使函数简化
*/
vector<vector<int>> dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int helper(vector<vector<int>>& matrix, vector<vector<int>>& visit, int i, int j, int m, int n) {
if (visit[i][j] > 1) return visit[i][j];
int result = 1;
for (auto dir : dirs) {
int x = i + dir[0], y = j + dir[1];
if (x < 0 || x >= m || y < 0 || y >= n || matrix[i][j] > matrix[x][y])
continue;
result = max(result, helper(matrix, visit, i, j, m, n));
}
visit[i][j] = result;
return result;
}
int longestIncreasingPath2(vector<vector<int>>& matrix) {
int m = matrix.size();
if (m == 0) return 0;
int n = matrix[0].size(); int result = 0;
vector<vector<int>> visit(m, vector<int>(n, 0));
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
result = max(result, helper(matrix, visit, i, j, m, n));
}
} return result;
}

构造链表

以Populating Next Right Pointers in Each Node为例,贴上代码:

/**
* 递归实现
*
* @param root <#root description#>
*/
void createTree(TreeLinkNode *root) {
if (root->left == NULL || root->right == NULL)
return ; TreeLinkNode * left = root->left;
TreeLinkNode * right = root->right; left->next = right;
right->next = root->next ? root->next->left : NULL; createTree(root->left);
createTree(root->right);
} void connect2(TreeLinkNode *root) {
if (root == NULL)
return ;
root->next = NULL;
createTree(root);
}

删除无效括号

以Remove Invalid Parentheses为例,贴上代码:

/**
* DFS+剪枝
*
* @param pair 遇见括号的个数
* @param index 记录字符串s的当前位置
* @param remove_left 左括号需要删除的个数
* @param remove_right 右括号需要删除的个数
* @param s 原始字符串
* @param solution 生成字符串
* @param result 存储所有的字符串结果
*/
void helper(int pair, int index, int remove_left, int remove_right, const string& s, string solution, unordered_set<string> &result) {
if (index == s.size()) {
if (pair == 0 && remove_left == 0 && remove_right == 0)
result.insert(solution);
return;
} if (s[index] == '(') {
// 删除左边括号
if (remove_left > 0) helper(pair, index, remove_left - 1, remove_right, s, solution, result);
// 回溯
helper(pair + 1, index, remove_left, remove_right, s, solution + s[index], result);
}
else if (s[index] == ')') {
// 删除右边括号
if (remove_right > 0) helper(pair, index, remove_left, remove_right - 1, s, solution, result);
// 回溯
if (pair > 0) helper(pair - 1, index, remove_left, remove_right, s, solution + s[index], result);
}
else {
helper(pair, index, remove_left, remove_right, s, solution + s[index], result);
} }
vector<string> removeInvalidParentheses(string s) {
int remove_left = 0, remove_right = 0, pair = 0;
unordered_set<string> result; // 处理重复 // 计算左右两边需要删除括号的个数
for (int i = 0; i < s.size(); ++i) {
if (s[i] == '(')
remove_left++;
else if (s[i] == ')')
if (remove_left > 0) remove_left--;
else remove_right++;
} helper(0, 0, remove_left, remove_right, s, "", result); return vector<string>(result.begin(), result.end());
}

总结

回溯法 = 深搜+剪枝

递归一定是深搜,深搜不一定是递归,因为还可以迭代实现;

递归有两种加速策略,一种是剪枝,对中间结果进行判断,提前返回;一种是缓存,缓存中间结果,防止重复计算,用空间换时间;

递归加缓存,就是memorization,即自顶向下+缓存,memorization不一定用递归,就像深搜不一定用递归,可以在迭代中使用memorization,递归也不一定memorization,可以用memorization加速,但不是必须的;

DFS 算法总结的更多相关文章

  1. BFS/DFS算法介绍与实现(转)

    广度优先搜索(Breadth-First-Search)和深度优先搜索(Deep-First-Search)是搜索策略中最经常用到的两种方法,特别常用于图的搜索.其中有很多的算法都用到了这两种思想,比 ...

  2. 图结构练习——判断给定图是否存在合法拓扑序列(dfs算法(第一个代码),邻接矩阵(前两个代码),邻接表(第三个代码))

    sdut 2140 图结构练习——判断给定图是否存在合法拓扑序列 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描述  给定一个有向图 ...

  3. DFS算法(——模板习题与总结)

    首先,需要说明的是搜索算法本质上也是枚举的一种,时间复杂度还是很高的,遇到问题(特别是有水平的比赛上),不要优先使用搜索算法. 这里总结一下DFS算法: 1.从图中某个顶点出发,访问v. 2.找出刚访 ...

  4. UVA 291 The House Of Santa Claus(DFS算法)

    题意:从 节点1出发,一笔画出 圣诞老人的家(所谓一笔画,就是遍访所有边且每条边仅访问一次). 思路:深度优先搜索(DFS算法) #include<iostream> #include&l ...

  5. POJ 3620 Avoid The Lakes(dfs算法)

    题意:给出一个农田的图,n行m列,再给出k个被淹没的坐标( i , j ).求出其中相连的被淹没的农田的最大范围. 思路:dfs算法 代码: #include<iostream> #inc ...

  6. DFS 算法模板

    dfs算法模板: 1.下一层是多节点的dfs遍历 def dfs(array or root, cur_layer, path, result): if cur_layer == len(array) ...

  7. 【2018.07.29】(深度优先搜索/回溯)学习DFS算法小记

    参考网站:https://blog.csdn.net/ldx19980108/article/details/76324307 这个网站里有动态图给我们体现BFS和DFS的区别:https://www ...

  8. DFS算法-求集合的所有子集

    目录 1. 题目来源 2. 普通方法 1. 思路 2. 代码 3. 运行结果 3. DFS算法 1. 概念 2. 解题思路 3. 代码 4. 运行结果 4. 对比 1. 题目来源 牛客网,集合的所有子 ...

  9. dfs算法

    一般bfs算法都是使用递归 //下面简单的代码 visited[Max]; dfs(_graph g,int vo){ print(vo); visited[vo]=1 for(int i=0;i&l ...

随机推荐

  1. C++ 内链接 外链接

    编译的时候(假如编译器是VS),是以源文件cpp文件为单位,编译成一个个的obj文件,然后再通过链接器把不同的obj文件链接起来.如果一些变量或函数的定义是内连接的话,链接器链接的时候就不会拿它们去与 ...

  2. ActiveSync 学习记录

    协议就是一种规范.它是高效团队协作的依据.有的人可能不爱看团队规范之类的文档,一方面是个人意识问题,另外也和文档的组织.协作的效果相关. 写好文档: 看好文档. 1. 处理XML转码 抓包后,发现邮件 ...

  3. data-key

    在foreach或者each循环中给按钮赋予值 html中:data-key="@config.key" js里获取值: var key = $(this).data(" ...

  4. JAVA代码中可使用中文类名,变量名,对象名,方法名.

    java程序 兔子 public class 兔子{ //构造方法 public 兔子(){} public void 吃草(){ System.out.println("兔子在吃草&quo ...

  5. The.Glory.of.Innovation 创新之路2科学基石

    犹太民族很早就确立了他们的生存法则:资源.土地,以及一切有形的东西都会消失,一个人最重要的财富是自己的头脑.是知识.是创造.   有些选择是被动的,有些选择是主动的,一旦决心要把技术变成自己的,独立的 ...

  6. java.lang.NoClassDefFoundError: javax/servlet/AsyncListener解决方案

    问题:spring3.2的架构在tomcat6.0中无法正常启动,抛出java.lang.NoClassDefFoundError: javax/servlet/AsyncListener错误 原因: ...

  7. 虚拟机下安装Centos7并配置Apache+PHP+Mysql+phpmyadmin+wordpress

    一.安装Apache yum install httpd 安装成功后,Apache操作命令: systemctl start httpd //启动apache systemctl stop httpd ...

  8. Debug.Assert vs Exception Throwing(转载)

    来源 Q: I've read plenty of articles (and a couple of other similar questions that were posted on Stac ...

  9. 导出CSV乱码

    导出CSV,无论是什么格式,excel打卡都是乱码 需要加上 echo "\xEF\xBB\xBF"; header("Content-Disposition:attac ...

  10. Google搜索

    https://www.google.com/intl/br/insidesearch/tipstricks/all.html 如何用好谷歌等搜索引擎?