算法系列之深度优先和广度优先

1. 前言

在图论和算法领域,深度优先搜索(DFS)和广度优先搜索(BFS)是两种基础而强大的遍历策略。它们就像探索未知世界的两种不同性格的冒险家:DFS像一位执着的前行者,一条路走到底再回头;BFS则像一位谨慎的规划者,一层一层向外扩展。本文将深入探讨这两种算法的原理、实现和应用,帮助你全面理解它们的异同点。

2. 深度优先搜索 (DFS - Depth-First Search)

  • 核心思想: “一条路走到黑,碰壁再回头”。
  • 工作流程
    1. 从起始节点开始。
    2. 随机选择一个相邻的未访问节点,深入访问它。
    3. 对新访问的节点重复步骤2(递归或使用栈),不断向深处探索。
    4. 当到达一个没有未访问相邻节点的节点(死胡同)时,回溯到最近一个有未访问相邻节点的节点。
    5. 重复步骤2-4,直到所有节点都被访问。
  • 数据结构: 使用栈 (Stack)(显式栈或递归调用时的隐式函数调用栈)。
  • 特点:
    • 空间复杂度较低: 通常只需要存储从根到当前节点的路径。在树或分支因子较小的图上空间复杂度为 O(h)(h 为最大深度),在一般图上最坏为 O(V)(V 为顶点数)。
    • 可能找到非最短路径: 它找到的解路径不一定是最短路径。
    • 实现简单(递归): 递归实现非常直观。
    • 适用于
      • 寻找所有解或路径是否存在(如迷宫求解)。
      • 拓扑排序。
      • 检测图中的环。
      • 查找连通分量。
      • 解决某些需要探索所有可能性的问题(如八皇后、数独)。
      • 图的强连通分量(Kosaraju 或 Tarjan 算法)。

3. 广度优先搜索 (BFS - Breadth-First Search)

  • 核心思想: “层层递进,由近及远”。
  • 工作流程
    1. 从起始节点开始,将其放入队列。
    2. 从队列中取出一个节点进行访问。
    3. 将这个节点的所有未访问的相邻节点放入队列。
    4. 重复步骤2和3,直到队列为空。
    5. 数据结构: 使用队列 (Queue)。
  • 特点
    • 找到最短路径(未加权图): 当图的边没有权重(或权重都相等)时,BFS 从起点开始按层遍历,首次访问到某个节点所经过的路径就是最短路径。
    • 空间复杂度较高: 在最坏情况下(如完全图),队列需要存储几乎所有的节点,空间复杂度为 O(V)。
    • 适用于:
      • 查找未加权图中的最短路径(最重要应用)。
      • 查找所有节点到起点的最短距离(同样在未加权图中)。
      • 网络爬虫(按层级抓取网页)。
      • 广播网络(如洪水填充算法、寻找最短广播路径)。
      • 垃圾回收中的可达性分析。
      • 点对点网络查找邻居节点。
      • 测试图的二分性。

4. 关键对比总结

特性深度优先搜索 (DFS)广度优先搜索 (BFS)
核心策略深度优先 (纵向探索)广度优先 (横向探索)
数据结构栈 (递归/显式栈)队列
空间效率通常较低 (O(h) 或 O(V))通常较高 (O(V), 最坏接近 O(V))
最短路径不一定找到最短路径在未加权图中保证找到最短路径
访问顺序无固定顺序,取决于实现和选择策略按距离起点层级递增的顺序访问
适用场景路径存在性、所有解、拓扑排序、环检测未加权图最短路径、层级遍历、广播、爬虫
类比走迷宫 (选一条路走到头再试下一条)水面涟漪扩散 (一圈一圈向外)

5. 测试用例

        A
       / \
      B   C
     / \   \
    D   E   F

DFS 遍历顺序 (假设先左后右): A -> B -> D -> E -> C -> F

  • 从 A 开始,深入访问左子树 B。
  • 在 B,深入访问左孩子 D(尽头)。
  • 回溯到 B,访问右孩子 E(尽头)。
  • 回溯到 A,访问右子树 C。
  • 在 C,深入访问右孩子 F(尽头)。

BFS 遍历顺序: A -> B -> C -> D -> E -> F

  • 访问 A (层0)。
  • 访问 A 的邻居 B, C (层1)。
  • 访问 B 的邻居 D, E (层2)。
  • 访问 C 的邻居 F (层2)。

基础图数据结构

struct GraphNode {
    char val;
    std::vector<GraphNode *> neighbors;
    GraphNode(char v = '\0') : val(v) {}
};

6. 深度优先搜索 (DFS) C++代码实现

void dfs(GraphNode *root, std::vector<char> &result,
         std::set<GraphNode *> &visited) {
    if (!root) {
        return;
    }
    visited.insert(root);
    result.push_back(root->val);
    for (auto neighbor : root->neighbors) {
        if (visited.find(neighbor) == visited.end()) {
            visited.insert(neighbor);
            dfs(neighbor, result, visited);
        }
    }
}

std::vector<char> dfs(GraphNode *root) {
    std::set<GraphNode *> visited;
    std::vector<char> result;
    dfs(root, result, visited);
    return result;
}

7. 广度优先搜索 (BFS) C++代码实现

std::vector<char> bfs(GraphNode *root) {
    std::vector<char> result;
    if (!root) {
        return result;
    }
    std::queue<GraphNode *> q;
    std::set<GraphNode *> visited;
    visited.insert(root);
    q.push(root);

    while (!q.empty()) {
        auto current = q.front();
        q.pop();
        result.push_back(current->val);
        for (auto neighbor : current->neighbors) {
            if (visited.find(neighbor) == visited.end()) {
                visited.insert(neighbor);
                q.push(neighbor);
            }
        }
    }

    return result;
}

测试主函数代码:

GraphNode *initGraph(const std::string &str) {
    int size = str.size();
    if (size == 0) {
        return nullptr;
    }
    int idx = 0;
    char ch = str[idx++];
    GraphNode *root = new GraphNode(ch);
    ch = str[idx++];
    if (ch == '(') {
        do {
            if (ch == ',') {
                ch = str[idx++];
                continue;
            }
            auto sstr = substr(str, idx);
            root->neighbors.push_back(initGraph(sstr));
            ch = str[idx];
        } while (idx < size);
    }

    return root;
}

void printResult(const std::vector<char> &result,
                 const std::string &perfix = "") {
    if (perfix.size() > 0) {
        std::cout << perfix;
    }
    int size = result.size();
    for (int i = 0; i < size; i++) {
        std::cout << result[i];
        if (i != size - 1) {
            std::cout << "-->";
        } else {
            std::cout << std::endl;
        }
    }
}

void destroyGraph(GraphNode *root) {
    if (!root) {
        return;
    }
    for (auto &neighbor : root->neighbors) {
        destroyGraph(neighbor);
    }
    delete root;
    root = nullptr;
}

int main() {
    std::string gstr("A(B(D,E),C(F))");
    auto *graph = initGraph(gstr);
    printf("graph = %p\n", graph);
    auto bfs_result = bfs(graph);
    printf("graph = %p\n", graph);
    auto dfs_result = dfs(graph);
    printResult(bfs_result, "BFS: ");
    printResult(dfs_result, "DFS: ");
    destroyGraph(graph);
    return 0;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值