c++开发进阶深度优先搜索(DFS)

深度优先搜索(DFS)算法详解

1. 深度优先搜索(DFS)的基本概念

DFS 是一种用于图(Graph)和树(Tree)数据结构中的遍历算法。它尽可能地沿着一个分支深入下去,直到遇到没有未访问的邻接节点时回溯,继续探索其他分支,直到所有节点都被访问。

DFS 的实现通常基于以下两种方式之一:

  • 递归(利用函数调用栈隐式地进行回溯)
  • 显式栈(使用自定义的栈来模拟递归)
2. DFS 的工作原理
  • 从图的起始节点开始访问。
  • 标记当前节点为已访问。
  • 遍历当前节点的所有邻居,对于每个邻居,如果尚未访问,递归地进行深度优先搜索。
  • 如果该节点的所有邻居都已访问,递归结束,自动回溯到上一个节点,继续搜索其他分支。
3. DFS 的应用场景
  • 连通性检测:检查图中是否存在连通分量。
  • 拓扑排序:在有向无环图(DAG)中,利用 DFS 来确定节点的线性排序。
  • 路径查找:用于找到从起点到目标点的路径。
  • 图的强连通分量:通过 DFS 可以找到图的强连通分量。
4. DFS 的复杂度分析
  • 时间复杂度:O(V + E),其中 V 是节点数,E 是边数。每个节点和每条边都最多被访问一次。
  • 空间复杂度:O(V),递归调用栈的深度为 V,另外还需要存储访问标记。
5. DFS 的实现
(1) 递归实现

递归实现利用函数调用栈来隐式地进行回溯。以下代码示例展示了递归实现的 DFS:

#include <iostream>
#include <vector>

using namespace std;

void DFSUtil(int iNode, const vector<vector<int>>& vvGraph, vector<bool>& vbVisited)
{
    // 标记当前节点为已访问
    vbVisited[iNode] = true;
    cout << iNode << " ";

    // 遍历当前节点的所有邻居节点
    for(int iNeighbor : vvGraph[iNode])
    {
        if(!vbVisited[iNeighbor])
        {
            // 递归调用DFS访问邻居
            DFSUtil(iNeighbor, vvGraph, vbVisited);
        }
    }
}

void DFS(const vector<vector<int>>& vvGraph, int iStartNode)
{
    int iTotalNodes = vvGraph.size();
    
    // 创建访问标记数组
    vector<bool> vbVisited(iTotalNodes, false);

    // 从起始节点执行DFS
    DFSUtil(iStartNode, vvGraph, vbVisited);
}

int main()
{
    // 创建图的邻接表
    vector<vector<int>> vvGraph = {
        {1, 2},      // 节点 0 与节点 1、2 相连
        {0, 2, 3},   // 节点 1 与节点 0、2、3 相连
        {0, 1, 4},   // 节点 2 与节点 0、1、4 相连
        {1, 5},      // 节点 3 与节点 1、5 相连
        {2},         // 节点 4 与节点 2 相连
        {3}          // 节点 5 与节点 3 相连
    };

    cout << "DFS starting from node 0: ";
    DFS(vvGraph, 0);

    return 0;
}
(2) 显式栈实现

DFS 也可以通过显式栈来实现,而不是依赖递归。这种方式通常被用于避免递归过深带来的栈溢出问题:

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

void DFSIterative(const vector<vector<int>>& vvGraph, int iStartNode)
{
    int iTotalNodes = vvGraph.size();

    // 访问标记数组
    vector<bool> vbVisited(iTotalNodes, false);

    // 栈用于存储待访问的节点
    stack<int> sNodes;
    sNodes.push(iStartNode);

    while(!sNodes.empty())
    {
        // 获取栈顶元素并访问
        int iCurrentNode = sNodes.top();
        sNodes.pop();

        // 如果当前节点尚未访问,访问它
        if(!vbVisited[iCurrentNode])
        {
            cout << iCurrentNode << " ";
            vbVisited[iCurrentNode] = true;
        }

        // 将当前节点的邻居节点压入栈
        for(int iNeighbor : vvGraph[iCurrentNode])
        {
            if(!vbVisited[iNeighbor])
            {
                sNodes.push(iNeighbor);
            }
        }
    }
}

int main()
{
    // 创建图的邻接表
    vector<vector<int>> vvGraph = {
        {1, 2},      // 节点 0 与节点 1、2 相连
        {0, 2, 3},   // 节点 1 与节点 0、2、3 相连
        {0, 1, 4},   // 节点 2 与节点 0、1、4 相连
        {1, 5},      // 节点 3 与节点 1、5 相连
        {2},         // 节点 4 与节点 2 相连
        {3}          // 节点 5 与节点 3 相连
    };

    cout << "Iterative DFS starting from node 0: ";
    DFSIterative(vvGraph, 0);

    return 0;
}
6. 代码详解
  • DFSUtil 函数(递归版):这是递归调用的核心函数。它接受当前节点、图的邻接表和访问标记数组为参数,递归地访问每个未访问的邻居。

  • DFSIterative 函数(栈版):栈版 DFS 使用栈来模拟递归的过程,每次从栈中取出一个节点,访问其邻居,并将未访问的邻居压入栈。

7. 注意事项
  • 图中的环:如果图中存在环(即有重复路径),DFS 会陷入无限循环。因此,访问标记数组非常重要,用来防止重复访问节点。
  • 图的连通性:如果图是非连通的,仅从一个起点进行 DFS 可能不会遍历所有节点。对于非连通图,通常需要对每个节点都进行一次 DFS。
8. 递归 vs 栈实现
  • 递归实现:代码简洁,但可能由于递归深度过大而导致栈溢出。
  • 显式栈实现:避免了递归深度的限制,更加健壮,但代码较为冗长。
9. DFS 的局限性
  • 适用场景:DFS 更适合用于寻找解而非最优解,因为它优先遍历到最深的节点。如果需要寻找最短路径,广度优先搜索(BFS)更适合。DFS 主要用于遍历和路径存在性检测等。
10. 总结

DFS 是一种强大的遍历算法,广泛应用于图和树的遍历中。通过递归或栈的方式,它能够高效地遍历整个图,同时也为许多复杂问题提供了基础。

DFS 算法通常是图论中很多高级算法的基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值