深度优先搜索

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();
                }
            }
        }
    };

}

参考文献

DFS-Deep First Search-深度优先搜索