Depth-first traversal and breadth-first traversal of undirected graphs

Introduction

Depth-First Search (DFS) and Breadth-First Search (BFS) are two commonly used methods in graph traversal algorithms. They can be used to search and traverse vertices in a graph, and each method has its own unique characteristics and application scenarios.

Depth-first traversal

Depth-first traversal is a recursive traversal algorithm. It starts from a certain vertex of the graph and visits the vertex as deeply as possible along a path until it cannot go any further. Then it goes back to the previous vertex and continues to visit other unvisited vertices until all vertices have been visited.

Analysis of ideas:

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

recursive implementation

Sample code:

#include <iostream>
#include <vector>

using namespace std;

void dfs(vector<vector<int>> &graph, vector<bool> &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<vector<int>> &graph) {
    int numVertices = graph.size();
    vector<bool> 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<vector<int>> graph = {
        {1, 2},
        {0},
        {0},
        {4},
        {5},
        {4}
    };
    dfsTraversal(graph);

    return 0;
}

Output result (connected component sought):

connected components:012
connected components:345

Summary: The advantage of using recursion is that it is easier to implement. However, there are also some disadvantages. If the depth is too deep, it will cause stack overflow. In this case we would consider using BFS, or implementing DFS using an explicit stack. (In fact, what we used above is the implicit stack provided by the system, which is also called the call stack Call Stack)

show stack implementation

We only need to modify the above dfs function.

Sample code:

void dfs(vector<vector<int>> &graph, vector<bool> &visited, int vertex) {
    // 将根顶点压栈
    stack<int> 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);
                }
            }
        }
    }
}

Summary: Using the display stack to implement recursion has the following advantages

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

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

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

breadth-first traversal

Breadth-first traversal is an iterative traversal algorithm. It starts from a certain vertex of the graph, first visits all the neighbors of the vertex, and then visits the neighbors’ neighbors in turn, and so on, until all reachable vertices are traversed.

Analysis of ideas:

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

Sample code:

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

using namespace std;

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

    queue<int> 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<vector<int>> &graph) {
    int numVertices = graph.size();
    vector<bool> 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<vector<int>> graph = {
        {1, 2},
        {0},
        {0},
        {4},
        {5},
        {4}
    };
    bfsTraversal(graph);

    return 0;
}

Compared

Depth-first traversal (DFS) and breadth-first traversal (BFS) are two commonly used graph traversal algorithms, and they have some differences in traversal order, implementation and application scenarios.

Traversal order:

  • DFS: Depth-first traversal starts from the starting node, traverses as deeply as possible along a path, until it can no longer go deeper, then backtracks to the previous node, and continues to traverse other paths. DFS will prioritize exploring depth over breadth.
  • BFS: Breadth-first traversal starts from the starting node, traverses the nodes in the graph layer by layer, first visits the node closest to the starting node, and then gradually expands to nodes farther away from the starting node. BFS will prioritize exploring breadth over depth.

Method to realize:

  • DFS: Depth-first traversal is usually implemented using recursion or a stack. The recursive method uses the system's function call stack to save the traversed path, while the stack method explicitly uses the stack data structure to save the traversed path.
  • BFS: Breadth-first traversal is usually implemented using queues. The queue is traversed in the order of arrival of the nodes, and the first-in-first-out principle ensures that the nodes visited first are processed first.

Application scenario:

  • DFS: Depth-first traversal is often used to find paths in graphs, topological sorting, connectivity detection and other problems. It can help us find a path in a graph, or judge whether a graph is connected or not.
  • BFS: Breadth-first traversal is often used to find the shortest path in the graph, connectivity detection and other problems. It helps us find the shortest path from a starting node to other nodes and can be used to solve some optimization problems.

In general, DFS and BFS are different in traversal order, implementation and application scenarios. DFS pays more attention to depth, while BFS pays more attention to breadth. The choice of which traversal algorithm to use depends on the specific problem requirements and the characteristics of the graph.

Guess you like

Origin blog.csdn.net/bmseven/article/details/131380764