#c

深度优先搜索基础

DFS思想与经典例题

#c
@鞠大龙
魅客科创中心

CONTENTS

目录

1. DFS概述

1.1 DFS概述

  • 深度优先搜索(DFS):一种系统地搜索所有可能性的算法,按深度优先的策略遍历解空间
  • 核心思想
    • 将问题分解为多个子问题
    • 优先完整地解决一个子问题
    • 当无法继续时返回上一步,尝试其他可能
  • 实现方式:通常使用递归实现,也可以用栈来模拟
  • 特点
    • 代码简洁,易于实现
    • 空间复杂度较低
    • 适合搜索所有可能解
返回目录 信息学 深度优先搜索基础 魅客科创中心

1.2 DFS的应用场景

  • 排列组合问题
    • 生成全排列
    • 生成所有子集
    • 数字组合问题
  • 搜索问题
    • 数字的所有可能拆分
    • 字符串的所有可能划分
    • 数独求解
  • 约束满足问题
    • N皇后问题
    • 0-1背包问题
    • 硬币找零问题
返回目录 信息学 深度优先搜索基础 魅客科创中心

2. DFS的基本类型

2.1 基础数据结构的DFS

  • 数组的DFS
    • 在数组元素间进行深度优先搜索
    • 常见操作:选择/不选择元素、排列元素
    • 应用:子集生成、排列生成
  • 字符串的DFS
    • 在字符串上进行深度优先搜索
    • 常见操作:分割字符串、字符排列
    • 应用:字符串分割、单词拆分
// 数组子集生成的DFS
void subsetDFS(vector<int>& nums, int index, 
               vector<int>& current, vector<vector<int>>& result) {
    // 将当前子集加入结果
    result.push_back(current);
    
    // 从index开始尝试添加每个元素
    for (int i = index; i < nums.size(); i++) {
        // 选择当前元素
        current.push_back(nums[i]);
        // 递归处理剩余元素
        subsetDFS(nums, i + 1, current, result);
        // 回溯,撤销选择
        current.pop_back();
    }
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

2.2 状态空间DFS

  • 定义:在抽象的状态空间中进行深度优先搜索
  • 特点
    • 状态表示问题的一个可能解
    • 通过转移规则从一个状态到另一个状态
    • 需要剪枝优化,避免搜索空间爆炸
  • 典型例子
    • 排列/组合生成
    • N皇后问题
    • 数独求解
    • 0-1背包问题
// 生成所有排列的DFS
void permuteDFS(vector<int>& nums, vector<bool>& used, 
                vector<int>& path, vector<vector<int>>& result) {
    if (path.size() == nums.size()) {
        result.push_back(path);
        return;
    }
    
    for (int i = 0; i < nums.size(); i++) {
        if (used[i]) continue;
        used[i] = true;
        path.push_back(nums[i]);
        permuteDFS(nums, used, path, result);
        path.pop_back();
        used[i] = false;
    }
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

2.3 回溯法DFS

  • 定义:一种通过试错来解决问题的DFS方法
  • 特点
    • 尝试分步解决问题
    • 当发现当前路径不可行时,回退到上一步
    • "做选择-递归-撤销选择"的模式
  • 典型例子
    • 子集生成
    • 组合问题
    • 分割问题
    • 解决约束满足问题
// 生成所有子集的回溯法
void subsetDFS(vector<int>& nums, int start, 
               vector<int>& path, vector<vector<int>>& result) {
    result.push_back(path);
    
    for (int i = start; i < nums.size(); i++) {
        path.push_back(nums[i]);
        subsetDFS(nums, i + 1, path, result);
        path.pop_back();
    }
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

2.4 特殊DFS类型

  • 记忆化搜索

    • 结合动态规划思想的DFS
    • 存储已计算状态的结果,避免重复计算
    • 适用于有重叠子问题的搜索
    • 例:记忆化斐波那契数列计算
  • 双向DFS

    • 同时从起点和终点开始搜索
    • 当两个搜索相遇时找到解
    • 可以显著减少搜索空间
    • 例:双向BFS寻找最短路径
  • 迭代加深DFS (IDDFS)
    • 结合DFS和BFS的优点
    • 逐步增加搜索深度限制
    • 兼具DFS的空间效率和BFS的完备性
    • 例:在深度未知的树中寻找目标节点
// 迭代加深DFS示例
bool IDDFS(TreeNode* root, int target, int maxDepth) {
    for (int depth = 0; depth <= maxDepth; depth++) {
        if (DLS(root, target, depth)) return true;
    }
    return false;
}

bool DLS(TreeNode* node, int target, int depth) {
    if (depth < 0) return false;
    if (node == nullptr) return false;
    if (node->val == target) return true;
    return DLS(node->left, target, depth-1) || 
           DLS(node->right, target, depth-1);
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

3.1 DFS的设计步骤

3.2 DFS的设计步骤

  1. 定义状态:确定搜索过程中需要维护的状态变量
  2. 设计递归函数:明确函数参数和返回值
  3. 确定基本情况:设置递归的终止条件
  4. 状态转移:定义如何从当前状态转移到下一状态
  5. 回溯处理:在返回上层前恢复状态(如需要)
  6. 剪枝优化:减少不必要的搜索分支
返回目录 信息学 深度优先搜索基础 魅客科创中心

4. DFS模板代码

4.1 DFS模板代码:数组/字符串处理

// 数组子集生成的DFS模板
void subsetDFS(vector<int>& nums, int index, vector<int>& current, vector<vector<int>>& result) {
    // 处理当前状态(例如:将当前子集加入结果)
    result.push_back(current);
    
    // 遍历所有可能的选择
    for (int i = index; i < nums.size(); i++) {
        // 做选择
        current.push_back(nums[i]);
        
        // 递归处理下一个状态
        subsetDFS(nums, i + 1, current, result);
        
        // 撤销选择(回溯)
        current.pop_back();
    }
    
    // 注意:基本情况(终止条件)通常在递归开始时检查
    // 例如:if (index == nums.size()) return;
}

// 字符串处理的DFS模板
void stringDFS(string& s, int start, vector<string>& current, vector<vector<string>>& result) {
    // 检查基本情况(终止条件)
    if (start >= s.length()) {
        result.push_back(current);
        return;
    }
    
    // 遍历所有可能的选择
    for (int i = start; i < s.length(); i++) {
        // 验证当前选择是否有效
        if (isValid(s, start, i)) {
            // 做选择
            current.push_back(s.substr(start, i - start + 1));
            
            // 递归处理下一个状态
            stringDFS(s, i + 1, current, result);
            
            // 撤销选择(回溯)
            current.pop_back();
        }
    }
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

4.2 DFS模板代码:回溯法

// 回溯法通用模板
void backtrack(vector<int>& nums, vector<bool>& used, vector<int>& path, vector<vector<int>>& result) {
    // 判断是否达到终止条件
    if (满足条件) {
        result.push_back(path);
        return;
    }
    
    for (int i = 0; i < nums.size(); i++) {
        // 剪枝:跳过不合法的选择
        if (不合法条件) continue;
        
        // 做选择
        used[i] = true;
        path.push_back(nums[i]);
        
        // 递归进入下一层决策树
        backtrack(nums, used, path, result);
        
        // 撤销选择(回溯)
        path.pop_back();
        used[i] = false;
    }
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

5. 优化技巧

5. 优化技巧

  • 剪枝:减少搜索空间
    • 可行性剪枝:提前判断当前路径是否可能得到解
    • 最优性剪枝:提前判断当前路径是否可能得到更优解
    • 对称性剪枝:跳过等价的搜索状态
  • 记忆化:存储已计算结果,避免重复计算
  • 启发式搜索:优先搜索更有希望的分支
  • 预处理:提前计算一些信息,加速搜索过程
返回目录 信息学 深度优先搜索基础 魅客科创中心

6. 经典例题

6.1 子集生成

问题描述:给定一个不含重复元素的整数数组,返回该数组所有可能的子集。

思路:使用DFS回溯法,对于每个元素,有"选"和"不选"两种选择。

代码实现

vector<vector<int>> subsets(vector<int>& nums) {
    vector<vector<int>> result;
    vector<int> current;
    
    // 从索引0开始DFS
    subsetDFS(nums, 0, current, result);
    return result;
}

void subsetDFS(vector<int>& nums, int index, 
               vector<int>& current, vector<vector<int>>& result) {
    // 将当前子集加入结果
    result.push_back(current);
    
    // 从index开始尝试添加每个元素
    for (int i = index; i < nums.size(); i++) {
        // 选择当前元素
        current.push_back(nums[i]);
        // 递归处理剩余元素
        subsetDFS(nums, i + 1, current, result);
        // 回溯,撤销选择
        current.pop_back();
    }
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

6.2 全排列

问题描述:给定一个不含重复数字的数组,返回其所有可能的全排列。

思路:使用回溯法,通过标记已使用的元素,递归生成所有排列。

代码实现

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> result;
    vector<int> path;
    vector<bool> used(nums.size(), false);
    
    backtrack(nums, used, path, result);
    return result;
}

void backtrack(vector<int>& nums, vector<bool>& used, 
               vector<int>& path, vector<vector<int>>& result) {
    if (path.size() == nums.size()) {
        result.push_back(path);
        return;
    }
    
    for (int i = 0; i < nums.size(); i++) {
        if (used[i]) continue;
        
        used[i] = true;
        path.push_back(nums[i]);
        
        backtrack(nums, used, path, result);
        
        path.pop_back();
        used[i] = false;
    }
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

6.3 数字组合

问题描述:给定一个正整数数组和目标数,找出所有可能的组合,使得组合中的数字和等于目标数。

思路:使用DFS回溯法,每个数字可以选择或不选择,当和等于目标数时记录组合。

代码实现

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    vector<vector<int>> result;
    vector<int> current;
    
    combinationDFS(candidates, target, 0, current, result);
    return result;
}

void combinationDFS(vector<int>& candidates, int target, int index,
                   vector<int>& current, vector<vector<int>>& result) {
    // 找到一个合法组合
    if (target == 0) {
        result.push_back(current);
        return;
    }
    
    // 尝试从index开始的每个数字
    for (int i = index; i < candidates.size(); i++) {
        // 剪枝:当前数字太大,跳过
        if (candidates[i] > target) continue;
        
        // 选择当前数字
        current.push_back(candidates[i]);
        // 递归处理剩余目标值
        combinationDFS(candidates, target - candidates[i], i, current, result);
        // 回溯,撤销选择
        current.pop_back();
    }
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

6.4 字符串分割

问题描述:给定一个字符串,将其分割成若干子串,使得每个子串都是回文串。返回所有可能的分割方案。

思路:使用DFS回溯法,在每个位置尝试分割,判断分割出的子串是否为回文串。

代码实现

vector<vector<string>> partition(string s) {
    vector<vector<string>> result;
    vector<string> current;
    
    partitionDFS(s, 0, current, result);
    return result;
}

void partitionDFS(string& s, int start, 
                  vector<string>& current, vector<vector<string>>& result) {
    // 已处理完整个字符串,记录结果
    if (start >= s.length()) {
        result.push_back(current);
        return;
    }
    
    // 尝试从start开始的每个可能的分割点
    for (int i = start; i < s.length(); i++) {
        // 判断子串是否为回文
        if (isPalindrome(s, start, i)) {
            // 选择当前分割方案
            current.push_back(s.substr(start, i - start + 1));
            // 递归处理剩余子串
            partitionDFS(s, i + 1, current, result);
            // 回溯,撤销选择
            current.pop_back();
        }
    }
}

bool isPalindrome(string& s, int start, int end) {
    while (start < end) {
        if (s[start++] != s[end--]) return false;
    }
    return true;
}
返回目录 信息学 深度优先搜索基础 魅客科创中心

7. 总结

7. 总结

  • DFS是一种重要的搜索算法,适用于各种图遍历和状态空间搜索问题
  • 掌握DFS的核心思想:深度优先、回溯、状态转移
  • 理解不同类型的DFS应用:图遍历、状态空间搜索、回溯法
  • 熟练运用剪枝和优化技巧,提高搜索效率
  • DFS与其他算法(如BFS、动态规划)结合,可以解决更复杂的问题
返回目录 信息学 深度优先搜索基础 魅客科创中心

8. 练习题目

8.1 初级练习题

  1. [HDU 1241] Oil Deposits(油田)

    • 难度:★★☆☆☆
    • 描述:给定一个由油田和空地组成的网格,统计油田连通块数量
    • 思路:使用网格DFS/Flood Fill,八方向搜索相连的油田格子
  2. [POJ 2386] Lake Counting(湖泊计数)

    • 难度:★★☆☆☆
    • 描述:在二维网格中统计由水域构成的连通块数量
    • 思路:网格DFS,四或八方向连通块遍历与计数
  3. [HDU 1312] Red and Black(红与黑)

    • 难度:★★☆☆☆
    • 描述:从起点出发,在棋盘上统计可达的格子数量
    • 思路:网格DFS,避免越界与访问重复,使用visited标记
返回目录 信息学 深度优先搜索基础 魅客科创中心

8.2 中级练习题

  1. [CSES] Counting Rooms(房间计数)

    • 难度:★★★☆☆
    • 描述:在由墙和空地组成的地图中统计房间数目
    • 思路:网格DFS/Flood Fill,连通块数量统计
  2. [CSES] Labyrinth(迷宫)

    • 难度:★★★☆☆
    • 描述:在迷宫中寻找从起点到终点的一条路径
    • 思路:图搜索,DFS可用于判定可达与记录路径(更优通常用BFS求最短)
  3. [CSES] Building Roads(修建道路)

    • 难度:★★★☆☆
    • 描述:给定一个无向图,连接所有连通分量使图连通
    • 思路:用DFS找出各连通分量代表点,然后输出需要连接的边
返回目录 信息学 深度优先搜索基础 魅客科创中心

8.3 高级练习题

  1. [USACO] Icy Perimeter(冰封边界)

    • 难度:★★★★☆
    • 描述:在字符网格中对连通区域进行面积与周长统计,找出最优区域
    • 思路:DFS/Flood Fill 统计每个连通块的面积与边界贡献
  2. [USACO] Closing the Farm(关闭农场)

    • 难度:★★★★☆
    • 描述:按给定顺序关闭点,判断图在每一步是否仍连通
    • 思路:使用DFS检查连通性(或反向加入并查集/DFS优化)
  3. [USACO] Moocast(奶牛通信)

    • 难度:★★★★☆
    • 描述:构建通信图,求每个节点能到达的最大数量
    • 思路:DFS遍历可达性,统计每个起点的到达规模
返回目录 信息学 深度优先搜索基础 魅客科创中心

8.4 综合应用题(CSP-J)

  1. [CSP-J] 迷宫路径搜索

    • 难度:★★★☆☆
    • 描述:在包含障碍的网格迷宫中判断从起点到终点是否可达
    • 思路:网格DFS判定可达性,记录路径或步数(最短路通常用BFS)
  2. [CSP-J] 图的连通块统计

    • 难度:★★★☆☆
    • 描述:给定无向图,统计连通分量数量
    • 思路:DFS遍历未访问节点,计数分量数
  3. [CSP-J] 岛屿数量扩展题

    • 难度:★★★★☆
    • 描述:网格上存在不同类型地形,统计指定类型的连通区域数量
    • 思路:多类别网格DFS,区分地形类型并进行连通块计数
返回目录 信息学 深度优先搜索基础 魅客科创中心
深度优先搜索

#c

演讲者备注不会在演示模式中显示给观众,仅供演讲者参考。 每个关键页面都添加了备注,帮助演讲者更好地讲解内容。

封面页: - 欢迎学生,简要介绍本次课程的主题和目标 - 强调DFS是算法基础,贯穿于各类搜索问题求解 - 本讲义将从DFS的基本概念、常见类型、设计方法到经典例题,系统讲解DFS思想

目录页: - 简要介绍讲义结构 - DFS基本概念、常见类型、设计步骤、模板代码、优化技巧、经典例题

转场页: - 引入DFS算法的基本概念 - DFS是算法设计的基础思想之一

DFS概述: - DFS的定义和核心思想 - DFS与BFS的区别

转场页: - DFS的常见类型

基础数据结构的DFS: - 数组的DFS - 字符串的DFS - 基础数据结构DFS的实现方式

状态空间DFS: - 定义和特点 - 常见的状态空间DFS例子 - 状态空间DFS的实现方式

回溯法DFS: - 定义和特点 - 常见的回溯法DFS例子 - 回溯法DFS的实现方式

特殊DFS类型: - 记忆化搜索 - 双向DFS - 迭代加深DFS

转场页: - DFS算法的设计流程

转场页: - DFS算法的代码模板

转场页: - DFS算法的优化方法

转场页: - DFS经典例题

转场页: - 总结DFS算法的核心思想和应用

转场页: - DFS算法练习题目 - 从各大OJ平台和竞赛中精选的DFS题目

练习题目部分结束,这些题目涵盖了DFS算法的各个难度和应用场景。 鼓励学生尝试解决这些问题,加深对DFS思想的理解。