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加速,但不是必须的;

最新文章

  1. Python 黑帽编程大纲(变化中)
  2. JS图片上传预览
  3. iOS 动态下载系统提供的中文字体
  4. 图说hibernate注释--xml里配置参数(一)
  5. Oracle之自动收集统计信息
  6. iOS项目上传到AppStore步骤流程
  7. c++中函数empty()怎么使用
  8. 【服务器防护】VPN的ip变更,导致无法连接服务器,解决方法【阿里云ECS】
  9. __LINE__ __DATE__ __FILE__ __TIME__ 等宏定义解释
  10. 微软云平台媒体服务实践系列 2- 使用动态封装为iOS, Android , Windows 等多平台提供视频点播(VoD)方案
  11. [BZOJ 1150] [CTSC2007] 数据备份Backup 【贪心 + 链表】
  12. Android Studio新手全然指引
  13. Windows Phone 8初学者开发—第22部分:用演示图板创建卷盘的动画
  14. Linux常用命令(第二版) --文件搜索命令
  15. 线上问题排查(2)——JDK内置工具
  16. 黄聪:OTP动态密码_Java代码实现
  17. WebViewJavascriptBridge浅析
  18. English trip V1 - 23. Big and Bigger Teacher:Corrine Key: adjective comparisons 形容词 比较级
  19. /src/log4j.xml
  20. IIS错误提示:另一个程序正在使用此文件 进程无法访问

热门文章

  1. Docker快速部署gitlab
  2. python调用PHP方法
  3. 配置web pack loader 报错:Module build failed: Error: The node API for `babel` has been moved to `babel-core`.
  4. Django的Session存储Redis环境配置
  5. Python_网络编程udp-飞秋自动攻击
  6. 51 nod 1181 质数中的质数(质数筛法)
  7. node.js(一)
  8. 删除TFS中的项目
  9. CentOS6.9安装Logstash
  10. Linux 的文件类型