深度优先搜索
DFS算法思想
- 深度优先搜索(缩写DFS)有点类似广度优先搜索,也是对一个连通图进行遍历的算法。
- 它的思想是从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,这种尽量往深处走的概念即是深度优先的概念。
使用场景
DFS适合此类题目:给定初始状态跟目标状态,要求判断从初始状态到目标状态是否有解。
输入数据 : 若是递归数据结构,如单链表,二叉树,集合,则百分百可以可以深搜;若是非递归数据结构,如一维数组,二维数组,字符串,图,则概念小一些.
状态转移图 : 树 or 图
求解目标 : 必须要走到最深 (例如,对于树,必须走到叶子节点,才能得到一个解) 这种情况非常适合深度搜索.
思考步骤
1 深度搜索常见的三个问题?
- 路径条数 即 可行解的总数
若是求路径条数,则不需要存储路径 - 求路径本身
若是求路径本身,需要一个数组 path[]
- 一个可行解
- 求所有可行解
2 只求一个解,还是求所有解?
- 如只求一个解. 找到返回即可.
广度优先搜索 一般只要求一个解. - 若求所有解. 找到以后继续扩展.
3 如何表示状态?
- 一个状态需要存储哪些必要的数据.
4 如何扩展状态?
- 数据不同,扩展方法不同
5 终止条件是什么?
- 到了不能扩展的末尾.
对于树,叶子节点.
对于图或者隐式图,是出度为0的节点.
6 收敛条件是什么?
- 收敛条件是指找到了一个合法解的时刻.
- 如果求一个合法解,找到一个直接返回.若是求所有解.就要收集解.即将path[] 放入解集合中.
7 关于判重?
- 是否需要判重?
- 怎样判重?
8 如何加速?
- 剪枝 ! 深度搜索一定要好好考虑如何剪枝.成本小,收益大. 加几行代码.能大大提速
- 缓存 !
前提条件: 状态转移是一个有向无权图(DAG). 存在重叠子问题. 子问题的解会被重复利用. 使用缓存自然会加速效果.
具体实现: 数组或者hashMap. C++11 中unordered_map比map快.
代码模板
/*
dfs模板:
[in] input 输入指针,
[out] path 当前路径,中间结果
[out] result 存储最终结果
[inout] cur or gap 标记当前位置或者距离目标的距离
[return] 路径长度,如求路径本身,则不需要返回长度
*/
void dfs(type &input, type &path, type &result, int cur or gap) {
if (数据非法) return 0;
if (cur == input.size()) {
// if(gap==0)
将path放入result
}
if (可以剪枝) return;
for () { // 执行所有可能的扩展动作
执行动作, 修改path
dfs(input, step + 1 or gap--, result);
恢复 path
}
}
深度搜索 vs 回溯法
- 回溯法 = 深度搜索 + 剪枝!
class Solution {
private:
int row, col;
vector < vector<bool> >visited;
vector<vector<int>> dirs = { { -1,0 },{ 0,1 },{ 1,0 },{ 0,-1 } };
bool inAera(int x, int y) {
return x >= 0 && x < row&&y >= 0 && y < col;
}
bool searchWord(vector<vector<char>>& board, string word, int index, int x, int y) {
if (index == word.size() - 1) {
return word[index] == board[x][y]; // 检查最后后一个字母是否正确
}
if (board[x][y] == word[index]) {
visited[x][y] = true;
for (auto d : dirs) {
int newx = x + d[0];
int newy = y + d[1];
if (inAera(newx, newy) && !visited[newx][newy]) {
if (searchWord(board, word, index + 1, newx, newy))
return true; // 找到了就返回真
}
}
visited[x][y] = false; // 撤销访问
}
return false;
}
public:
bool exist(vector<vector<char>>& board, string word) {
row = board.size();
col = board[0].size();
visited = vector<vector<bool>>(row, vector<bool>(col, false));
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (searchWord(board, word, 0, i, j))
return true; // 如果找到了就输返回真!
}
}
return false;
}
};
Palindrome Partitioning
// 动态规划 + 深度搜索
namespace pro131_1 {
class Solution {
public:
vector<vector<string>> res;
vector<vector<string>> partition(string s) {
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n, false));
// dp[j][i]表示 是否[i..j]的是回文串
for (int j = 0; j < n; j++) {
for (int i = 0; i <= j; i++) {
dp[j][i] = s[i] == s[j] && ((j - i) < 2 || dp[j - 1][i + 1]);
}
}
vector<string> path;
helper(path, s, dp, 0);
return res;
}
void helper(vector<string> path, string s,vector < vector<bool>> &dp, int start) {
if (start == s.size()) {
res.push_back(path);
return;
}
for (int i = start; i < s.size(); i++) {
if (dp[i][start]) {
string palindrome = s.substr(start, i - start + 1);
path.push_back(palindrome);
helper(path, s, dp, i + 1);
path.pop_back();
}
}
}
};
}
参考文献