无向图的深度优先遍历与广度优先遍历

简介

深度优先遍历(Depth-First Search,DFS)和广度优先遍历(Breadth-First Search,BFS)是图遍历算法中常用的两种方法。它们可以用于搜索和遍历图中的顶点,每种方法都有其独特的特点和应用场景。

深度优先遍历

深度优先遍历是一种递归的遍历算法,它从图的某个顶点开始,沿着一条路径尽可能深入地访问顶点,直到无法继续深入为止,然后回溯到上一个顶点,继续访问其他未被访问的顶点,直到所有顶点都被访问完毕。

思路解析:

1. 创建一个visited数组,用于记录顶点是否被访问。
2. 从graph中第一个未被访问顶点v0开始
3. 查找v0的第一个未被访问的邻接点,访问该顶点,并以该顶点为新顶点,依此循环,直到没有未被访问的邻接点。
4. 回到前一个访问过的仍有未被访问的邻接点,继续访问。
5. 重复步骤2,3,直至所有顶点均被访问。

递归实现

示例代码:

#include 
#include 

using namespace std;

void dfs(vector> &graph, vector &visited, int vertex) {
    visited[vertex] = true;

    for (int i = 0; i < graph[vertex].size(); i++) {
        int neighbor = graph[vertex][i];
        if (!visited[neighbor]) {
            dfs(graph, visited, neighbor);
        }
    }
}

void dfsTraversal(vector> &graph) {
    int numVertices = graph.size();
    vector visited(numVertices, false);

    for (int i = 0; i < numVertices; i++) {
        if (!visited[i]) {
            cout << "connected components:";
            dfs(graph, visited, i);
            cout << endl;
        }
    }
}

int main() {
    vector> graph = {
        {1, 2},
        {0},
        {0},
        {4},
        {5},
        {4}
    };
    dfsTraversal(graph);

    return 0;
}

输出结果(求的连通分量):

connected components:012
connected components:345

总结:使用递归的优点是它更容易实现。但是,也存在一些缺点,如果深度太深,会造成堆栈溢出。这种情况下我们会考虑使用 BFS,或使用显示栈实现 DFS。(其实我们上面使用的是系统提供的隐式栈,也成为调用栈 Call Stack)

显示栈实现

我们只需要修改上面 dfs 函数即可。

示例代码:

void dfs(vector> &graph, vector &visited, int vertex) {
    // 将根顶点压栈
    stack s;
    s.push(vertex);

    while (!s.empty()) {
        // 取栈顶元素
        int vertex = s.top();
        s.pop();

        if (!visited[vertex]) {
            visited[vertex] = true;
            cout << vertex << " ";

            for (int i = 0; i < graph[vertex].size(); i++) {
                int neighbor = graph[vertex][i];
                if (!visited[neighbor]) {
                    // 邻接点压栈
                    s.push(neighbor);
                }
            }
        }
    }
}

总结:使用显示栈实现递归有以下几个优点

- 避免栈溢出:递归算法在处理大规模问题时可能会导致栈溢出的问题,因为每次递归调用都会占用一定的栈空间。使用显示栈可以更好地控制栈的使用,从而避免栈溢出的风险。

- 提高性能:递归调用会引入函数调用和返回的开销,包括保存和恢复上下文、参数传递等。使用显示栈可以避免这些开销,因为所有的状态都显式地保存在栈中,而不是通过函数调用和返回来传递。这可以提高算法的性能。

- 灵活控制递归过程:使用显示栈可以手动控制递归的深度和顺序。递归函数的执行顺序是由函数调用和返回决定的,而使用显示栈可以在需要时手动推入和弹出栈帧,从而灵活控制递归的深度和顺序。

广度优先遍历

广度优先遍历是一种迭代的遍历算法,它从图的某个顶点开始,先访问该顶点的所有邻居,然后再依次访问邻居的邻居,以此类推,直到遍历完所有可达的顶点。

思路解析:

1. 创建一个visited数组,用于记录已经访问过的顶点。
2. 创建一个队列,并将起始顶点放入队列中。
3. 将起始顶点标记为已访问。
4. 从队列中取出一个顶点,访问该顶点,并将其所有未访问过的邻居顶点放入队列中。
5. 标记刚刚访问过的顶点为已访问。
6. 重复步骤4、5,直到队列为空。

示例代码:

#include 
#include 
#include 

using namespace std;

void bfs(vector> &graph, vector &visited, int start) {
    visited[start] = true;

    queue q;
    q.push(start);

    while (!q.empty()) {
        int vertex = q.front();
        q.pop();

        cout << vertex << " ";

        for (int i = 0; i < graph[vertex].size(); i++) {
            int neighbor = graph[vertex][i];
            if (!visited[neighbor]) {
                q.push(neighbor);
                visited[neighbor] = true;
            }
        }
    }
}

void bfsTraversal(vector> &graph) {
    int numVertices = graph.size();
    vector visited(numVertices, false);

    for (int i = 0; i < numVertices; i++) {
        if (!visited[i]) {
            cout << "connected components:";
            bfs(graph, visited, i);
            cout << endl;
        }
    }
}

int main() {
    vector> graph = {
        {1, 2},
        {0},
        {0},
        {4},
        {5},
        {4}
    };
    bfsTraversal(graph);

    return 0;
}

对比

深度优先遍历(DFS)和广度优先遍历(BFS)是两种常用的图遍历算法,它们在遍历顺序、实现方式和应用场景上有一些区别。

遍历顺序:

  • DFS:深度优先遍历从起始节点开始,沿着一条路径尽可能深入地遍历,直到无法继续深入为止,然后回溯到上一个节点,继续遍历其他路径。DFS会优先探索深度而不是广度。
  • BFS:广度优先遍历从起始节点开始,逐层地遍历图中的节点,先访问离起始节点最近的节点,然后逐渐扩展到离起始节点更远的节点。BFS会优先探索广度而不是深度。

实现方式:

  • DFS:深度优先遍历通常使用递归或栈来实现。递归方式会利用系统的函数调用栈来保存遍历的路径,而栈方式则显式地使用栈数据结构来保存遍历的路径。
  • BFS:广度优先遍历通常使用队列来实现。队列按照节点的到达顺序进行遍历,先进先出的原则确保先访问的节点先被处理。

应用场景:

  • DFS:深度优先遍历常用于寻找图中的路径、拓扑排序、连通性检测等问题。它可以帮助我们在图中找到一条路径,或者判断图是否是连通的。
  • BFS:广度优先遍历常用于寻找图中的最短路径、连通性检测等问题。它可以帮助我们找到起始节点到其他节点的最短路径,并且可以用于解决一些优化问题。

总的来说,DFS和BFS在遍历顺序、实现方式和应用场景上有所不同。DFS更注重深度,而BFS更注重广度。选择使用哪种遍历算法取决于具体的问题需求和图的特点。

你可能感兴趣的:(#,数据结构与算法,深度优先,宽度优先,算法)