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. hdu5758 思维,树形dp

    /*可以推测从叶子结点传送到叶子节点才能使传送次数最少,如果是偶数个叶子结点,那么传送leaf/2次就是答案,如果是奇数个叶子结点,则还有单独一条链需要覆盖dp[u]表示覆盖完u为根的子树需要走的边数 ...

  2. C++中的继承(1) 继承方式

    1.继承与派生  继承是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一.简单的说,继承是指一个对象直接使用另一对象的属性和方法.继承呈现了 面向对象程序设 计的层次结构, 体现了 由简单 ...

  3. Liunx 特殊权限 suid sgid t

    suid 在passwd 中体现,在执行命令的时候普通用户拥有管理员的权限 [root@test_android_client_download ~]# ll /usr/bin/passwd -rws ...

  4. webpack学习笔记--配置总结

    从前面的配置看来选项很多,Webpack 内置了很多功能. 你不必都记住它们,只需要大概明白 Webpack 原理和核心概念去判断选项大致属于哪个大模块下,再去查详细的使用文档. 通常你可用如下经验去 ...

  5. .Net Core下发送WebRequest请求的两种方式

    1.使用RestSharp.NetCore 2.使用WebApi请求方式

  6. babelrc

    .babelrc文件 // 简单版 { "presets": ["es2015", "stage-2"], // 使用 es2015 npm ...

  7. Tensorflow name_scope

    在 Tensorflow 当中有两种途径生成变量 variable, 一种是 tf.get_variable(), 另一种是 tf.Variable(). 使用tf.get_variable()定义的 ...

  8. jquery实时监听输入框值变化

    在做web开发时候很多时候都需要即时监听输入框值的变化,以便作出即时动作去引导浏览者增强网站的用户体验感.而采用onchange时间又往往是在输入框失去焦点(onblur)时候触发,有时候并不能满足条 ...

  9. Codeforces 1137D Cooperative Game (看题解)

    Cooperative Game 智商题, 感觉不太能推出来, 虽然看看证明过程是对的. #include<bits/stdc++.h> #define LL long long #def ...

  10. HttpWatch入门使用教程

    HttpWatch V10.0.20.0 官方免费版 HttpWatch是强大的网页数据分析工具.集成... HttpWatch Professional V10.0.20.0 官方下载 HttpWa ...