算法必学!DFS 和 BFS 全网最详细对比 + 代码模板

一、基本概念

深度优先搜索(Depth-First Search, DFS) 是一种用于遍历或搜索树、图等数据结构的算法。其核心思想是尽可能深地探索分支,直到无法继续前进时回溯到上一个分叉点,转而探索其他分支。

特点

  • 深度优先:优先沿一条路径深入到底,再回溯。
  • 栈结构:递归或显式栈实现,后进先出(LIFO)。
  • 空间复杂度:O(h),h为树的高度或图的深度。

二、算法流程
  1. 初始化:选择起点,标记为已访问
  2. 深入探索:访问当前节点的第一个未访问邻接节点。
  3. 回溯机制:若当前节点无未访问邻接节点,回溯至上一节点。
  4. 终止条件:所有节点均被访问。

(递归实现):

#include <iostream>
#include <vector>

const int MAXN = 100;
std::vector<int> graph[MAXN];
bool visited[MAXN];//标记数组,来记录结点是否已被访问

void dfs(int node) {//传递正在访问的结点和当前结点的深度
    visited[node] = true;
    std::cout << "访问节点: " << node << std::endl;
    for (int neighbor : graph[node]) {
        if (!visited[neighbor]) {
            dfs(neighbor);
        }
    }
}

int main() {
    int n, m;
    std::cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        int u, v;
        std::cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }
    for (int i = 0; i < n; ++i) {
        visited[i] = false;
    }
    dfs(0);
    return 0;
}

三、应用场景
  1. 路径搜索:迷宫问题、是否存在路径。
  2. 连通性检测:判断图的连通分量。
  3. 拓扑排序:有向无环图(DAG)的排序。
  4. 环检测:判断图中是否存在环。
  5. 回溯算法:八皇后、数独等组合问题。

四、DFS与BFS对比
特性DFSBFS
数据结构栈(递归/显式栈)队列
遍历顺序深度优先广度优先(逐层扩展)
空间复杂度O(h),h为树高或图深O(w),w为树宽或图广
适用场景探索所有可能路径、连通性检测最短路径、最小步数问题

五、代码示例

1. 树的DFS遍历(递归)

#include <iostream>
using namespace std;

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

// 前序遍历(根-左-右)
void dfs_preorder(TreeNode* root) {
    if (!root) return;
    cout << root->val << " ";  // 处理当前节点
    dfs_preorder(root->left);
    dfs_preorder(root->right);
}

// 中序遍历(左-根-右)
void dfs_inorder(TreeNode* root) {
    if (!root) return;
    dfs_inorder(root->left);
    cout << root->val << " ";  // 处理当前节点
    dfs_inorder(root->right);
}

int main() {
    /* 构建示例树:
           1
         /   \
        2     3
       / \   / 
      4  5 6  
    */
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->left->left = new TreeNode(4);
    root->left->right = new TreeNode(5);
    root->right->left = new TreeNode(6);

    cout << "前序遍历: ";
    dfs_preorder(root);  // 输出:1 2 4 5 3 6
    cout << "\n中序遍历: ";
    dfs_inorder(root);   // 输出:4 2 5 1 6 3
    return 0;
}
//运行结果:前序遍历: 1 2 4 5 3 6 
//中序遍历: 4 2 5 1 6 3 

2. 图的DFS遍历(显式栈)

#include <iostream>
#include <stack>
#include <vector>
#include <unordered_map>
using namespace std;

void dfs_graph(int start, const unordered_map<int, vector<int>>& graph) {
    stack<int> st;
    vector<bool> visited(graph.size() + 1, false);  // 假设节点编号从1开始
    
    st.push(start);
    visited[start] = true;
    
    while (!st.empty()) {
        int node = st.top();
        st.pop();
        cout << node << " ";  // 处理当前节点
        
        // 逆序压栈保证遍历顺序与递归一致
        for (auto it = graph.at(node).rbegin(); it != graph.at(node).rend(); ++it) {
            if (!visited[*it]) {
                visited[*it] = true;
                st.push(*it);
            }
        }
    }
}

int main() {
    /* 构建图的邻接表:
        1 → 2 → 5
        ↓   ↓ 
        3 ← 4 → 6
    */
    unordered_map<int, vector<int>> graph = {
        {1, {2, 3}},
        {2, {4, 5}},
        {3, {}},
        {4, {3, 6}},
        {5, {}},
        {6, {}}
    };

    cout << "DFS遍历结果: ";
    dfs_graph(1, graph);  // 输出:1 2 4 3 6 5
    return 0;
}
//运行结果:1 2 4 3 6 5 

六、优化技巧
  1. 剪枝(Pruning)
    在搜索过程中提前终止无效分支,减少计算量。
    示例:在组合问题中,若当前路径已不满足条件,停止深入。
  2. 记忆化(Memoization)
    缓存已计算结果,避免重复计算。常用于动态规划与DFS结合的场景。
  3. 迭代深化(IDDFS)
    结合BFS的层级限制,逐步增加深度限制,避免DFS陷入过深路径。

七、常见问题
  1. 栈溢出
    解决方法:使用显式栈替代递归,或调整递归深度限制。
  2. 重复访问
    解决方法:维护visited集合,标记已访问节点(尤其在图遍历中)。
  3. 路径记录
    技巧:使用栈或列表保存当前路径,回溯时弹出末尾节点。

八、总结
  • 优势:代码简洁(递归实现)、内存占用低、适合探索所有可能性。
  • 劣势:不保证最短路径、可能陷入深层无效搜索。
  • 核心口诀
    “一路到底再回溯,栈中探索不忘记。剪枝优化效率高,应用场景要选对。”

掌握DFS的关键在于理解其深度探索与回溯机制,并结合实际问题灵活运用剪枝和记忆化等优化策略。

一、基本概念

广度优先搜索(Breadth-First Search, BFS) 是一种用于遍历或搜索树、图等数据结构的算法。其核心思想是逐层探索节点,先访问离起点最近的节点,再依次向外扩展。BFS 保证找到最短路径(当边权重相等时),常用于解决最短路径问题

特点

  • 广度优先:逐层扩展,先近后远。
  • 队列结构:先进先出(FIFO),用队列实现。
  • 空间复杂度:O(w),w为树的最大宽度或图的广度。

二、算法流程
  1. 初始化:将起点加入队列,标记为已访问。
  2. 逐层扩展
    • 取出队首节点并处理。
    • 将该节点的所有未访问邻接节点加入队列。
  3. 终止条件:队列为空(所有可达节点已访问)。

伪代码

def bfs(start):
    queue = deque([start])
    visited = {start}
    while queue:
        node = queue.popleft()
        process(node)  # 处理当前节点
        for neighbor in node.neighbors:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

三、应用场景
  1. 无权图的最短路径:迷宫最短路径、社交网络六度空间。
  2. 连通性检测:判断图的连通分量。
  3. 层级遍历:二叉树的层序遍历、图的层级分析。
  4. 状态转移问题:华容道、魔方最少步数。

四、BFS与DFS对比
特性BFSDFS
数据结构队列栈(递归/显式栈)
遍历顺序层级遍历(广度优先)深度优先
空间复杂度O(w),w为最大宽度O(h),h为树高或图深
优势场景最短路径、层级分析探索所有路径、连通性检测

五、代码示例

1. 树的层序遍历(BFS)

#include <iostream>
#include <queue>
#include <vector>

using namespace std;

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> result;
    if (!root) return result;
    
    queue<TreeNode*> q;
    q.push(root);
    
    while (!q.empty()) {
        int level_size = q.size();
        vector<int> level;
        
        for (int i = 0; i < level_size; ++i) {
            TreeNode* node = q.front();
            q.pop();
            level.push_back(node->val);
            
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        
        result.push_back(level);
    }
    
    return result;
}

int main() {
    // 构建示例树:
    //       3
    //      / \
    //     9  20
    //       /  \
    //      15   7
    TreeNode* root = new TreeNode(3);
    root->left = new TreeNode(9);
    root->right = new TreeNode(20);
    root->right->left = new TreeNode(15);
    root->right->right = new TreeNode(7);
    
    vector<vector<int>> res = levelOrder(root);
    
    // 输出结果:
    // [3]
    // [9,20]
    // [15,7]
    for (auto& level : res) {
        for (int num : level) {
            cout << num << " ";
        }
        cout << endl;
    }
    
    return 0;
}

2. 迷宫最短路径(BFS)

#include <iostream>
#include <queue>
#include <vector>
#include <utility> // for pair

using namespace std;

int shortestPath(vector<vector<int>>& maze, pair<int, int> start, pair<int, int> end) {
    // 方向:上、下、左、右
    vector<pair<int, int>> dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
    
    int rows = maze.size();
    int cols = maze[0].size();
    
    // 访问标记数组
    vector<vector<bool>> visited(rows, vector<bool>(cols, false));
    
    queue<pair<pair<int, int>, int>> q; // ((x,y), steps)
    q.push({start, 0});
    visited[start.first][start.second] = true;
    
    while (!q.empty()) {
        auto [pos, steps] = q.front();
        auto [x, y] = pos;
        q.pop();
        
        if (x == end.first && y == end.second) {
            return steps;
        }
        
        for (auto [dx, dy] : dirs) {
            int nx = x + dx;
            int ny = y + dy;
            
            // 边界检查:未越界、可通行、未访问
            if (nx >= 0 && nx < rows && ny >= 0 && ny < cols 
                && maze[nx][ny] == 0 && !visited[nx][ny]) {
                visited[nx][ny] = true;
                q.push({{nx, ny}, steps + 1});
            }
        }
    }
    
    return -1; // 不可达
}

int main() {
    // 迷宫示例(0=可通行,1=障碍)
    vector<vector<int>> maze = {
        {0, 1, 0, 0},
        {0, 0, 0, 1},
        {1, 1, 0, 0},
        {0, 0, 0, 0}
    };
    
    pair<int, int> start = {0, 0}; // (行,列)
    pair<int, int> end = {3, 3};
    
    int steps = shortestPath(maze, start, end);
    cout << "Shortest path steps: " << steps << endl; // 应输出7
    
    return 0;
}

六、优化技巧
  1. 双向BFS(Bidirectional BFS)
    从起点和终点同时开始搜索,减少搜索空间。适用于已知终点的场景。
  2. 优先队列优化(Dijkstra)
    当边权重不相等时,使用优先队列代替普通队列,演变为Dijkstra算法。
  3. 剪枝(Pruning)
    提前终止无效路径的探索,例如在状态搜索中跳过重复状态。

七、常见问题
  1. 空间爆炸
    问题:当图的广度极大时(如指数级增长的节点数),队列可能占用过多内存。
    解决:结合DFS的迭代深化(IDDFS)或双向BFS。
  2. 重复访问
    关键:必须在入队时标记访问状态,而非出队时。否则可能导致同一节点多次入队。
  3. 无权图与有权图
    注意:BFS仅保证在无权图(或等权图)中找到最短路径,有权图需用Dijkstra或A*算法。

八、总结
  • 优势:保证最短路径、层级分析直观。
  • 劣势:空间复杂度较高,不适用于深度极大的场景。
  • 核心口诀
    “队列逐层扫,先近再远跑。最短路径它最强,层级遍历是绝招。”

掌握BFS的关键在于理解其层级遍历特性,并熟练应用队列数据结构。在解决最短路径、状态转移等问题时,BFS往往是更优选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值