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. HTML&javaSkcript&CSS&jQuery&ajax(九)

    一.HTML 1.单选按钮 <form action="><inpput type="radio" name="sex" value ...

  2. poj3107树的重心

    /*树的重心求法:两次dfs,第一次dfs处理出每个结点的size,以此求每个结点大儿子的size,第二次dfs将每个结点大儿子的size和余下结点数进行比较,所有结点里两个值之间差值最小的那个点就是 ...

  3. mysql 文件导入

    load data infile 文件路径 into table 表 fields terminated by ',' lines terminated '\n'

  4. bootstrap-table 刷新页面数据

    bom.bootstrapTable('load',msg['object']);//这一步 务必要添加. if(msg['code']==1){ bom.find('tbody').css('dis ...

  5. Python Day-1 练习

    作业1 要求:1.输入用户密码 2.认真成功后显示欢迎信息 3.输入三次锁定 代码如下: __author__ = 'zhang.ning' username = "zhangning&qu ...

  6. SqlServer 分页存储过程

    SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE PROCEDURE [dbo].[usp_CommonDataResourcePaged ...

  7. plsql的汉化问题

    1:汉化工具下载地址:https://www.allroundautomations.com/bodyplsqldevreg.html 汉化吗.然后选择chinese就行了: 2:对安装(解压缩好的哦 ...

  8. (原创)C# 压缩解压那些事儿

    吐槽: 搜狗推广API的报告服务太坑爹了!!! 搜狗推广API的报告服务太坑爹了!!! 搜狗推广API的报告服务太坑爹了!!! 搜狗的太垃圾了,获取下来的压缩包使用正常方式无法解压!!没有专门的API ...

  9. curl请求https请求

    function curl_https($url,$data){ $ch = curl_init (); curl_setopt ( $ch, CURLOPT_URL, $url ); curl_se ...

  10. lojround3

    A.绯色 IOI(开端) 首先注意到是完全图,数据范围又很大,肯定要观察一些性质 我们化简一下式子 发现其实是要求simga(xixj)最大 那么结论就很好想了 最大的和次大的第三大的连一起...然后 ...