Common algorithm techniques—graph algorithms

a brief introdction

Graph algorithm is a type of algorithm that solves graph theory problems. It is mainly used in graph traversal, path search, minimum spanning tree and other problems. A graph is a data structure composed of nodes (vertices) and edges connecting the nodes.

Algorithm classification

Below I will introduce several common graph algorithms and provide sample programs implemented in C++:

Depth First Search (DFS)

Depth-first search is an algorithm for graph traversal, which performs depth-first traversal of the graph through recursion or stack. Depth-first search starts from the starting node and visits the nodes of the graph as deep as possible along a path until it cannot continue, then backtracks to the previous node and continues to explore other paths. Here is an example of using depth-first search to find whether a path exists in a graph:

#include <iostream>
#include <vector>

bool dfs(std::vector<std::vector<int>>& graph, int start, int target, std::vector<bool>& visited) {
    
    
    if (start == target) {
    
    
        return true;  // 找到目标节点,存在一条路径
    }

    visited[start] = true;  // 标记当前节点为已访问

    for (int neighbor : graph[start]) {
    
    
        if (!visited[neighbor]) {
    
    
            if (dfs(graph, neighbor, target, visited)) {
    
    
                return true;  // 从相邻节点出发能够找到目标节点
            }
        }
    }

    return false;  // 从当前节点无法找到目标节点
}

int main() {
    
    
    std::vector<std::vector<int>> graph = {
    
    {
    
    1, 2}, {
    
    2, 3}, {
    
    3}, {
    
    }};
    int start = 0;
    int target = 3;

    int n = graph.size();
    std::vector<bool> visited(n, false);  // 记录节点的访问状态

    bool result = dfs(graph, start, target, visited);

    std::cout << std::boolalpha << result << std::endl;  // 输出:true

    return 0;
}

Breadth First Search (BFS)

Breadth-first search is an algorithm for graph traversal. It starts from the starting node and traverses the nodes of the graph layer by layer. It first visits the nodes closest to the starting node, and then gradually visits nodes with increasing distance. Breadth-first search uses queues to save nodes to be visited, ensuring that they are traversed in hierarchical order. Here is an example of using breadth-first search to find whether a path exists in a graph:

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

bool bfs(std::vector<std::vector<int>>& graph, int start, int target) {
    
    
    int n = graph.size();
    std::vector<bool> visited(n, false);  // 记录节点的访问状态

    std::queue<int> q;  // 创建队列用于广度优先遍历
    q.push(start);      // 将起始节点放入队列
    visited[start] = true;

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

        if (curr == target) {
    
    
            return true;  // 找到目标节点,存在一条路径
        }

        for (int neighbor : graph[curr]) {
    
    
            if (!visited[neighbor]) {
    
    
                q.push(neighbor);
                visited[neighbor] = true;
            }
        }
    }

    return false;  // 无法找到目标节点
}

int main() {
    
    
    std::vector<std::vector<int>> graph = {
    
    {
    
    1, 2}, {
    
    2, 3}, {
    
    3}, {
    
    }};
    int start = 0;
    int target = 3;

    bool result = bfs(graph, start, target);

    std::cout << std::boolalpha << result << std::endl;  // 输出:true

    return 0;
}

shortest path algorithm

Dijkstra algorithm and Bellman-Ford algorithm

Dijkstra's algorithm

Dijkstra's algorithm is used to solve the single-source shortest path problem, that is, the shortest path from a fixed starting node to all other nodes. It uses a greedy strategy to gradually update the shortest path length from the starting node to each node.

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

#define INF std::numeric_limits<int>::max()

void dijkstra(std::vector<std::vector<std::pair<int, int>>>& graph, int start, std::vector<int>& dist) {
    
    
    int n = graph.size();
    std::vector<bool> visited(n, false);
    std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> pq;

    dist[start] = 0;
    pq.push(std::make_pair(0, start));

    while (!pq.empty()) {
    
    
        int u = pq.top().second;
        pq.pop();

        if (visited[u]) {
    
    
            continue;
        }
        visited[u] = true;

        for (const auto& neighbor : graph[u]) {
    
    
            int v = neighbor.first;
            int weight = neighbor.second;

            if (dist[u] != INF && dist[u] + weight < dist[v]) {
    
    
                dist[v] = dist[u] + weight;
                pq.push(std::make_pair(dist[v], v));
            }
        }
    }
}

int main() {
    
    
    int n = 6;  // 节点数
    std::vector<std::vector<std::pair<int, int>>> graph(n);  // 邻接表表示的图

    // 添加边
    graph[0].push_back(std::make_pair(1, 2));
    graph[0].push_back(std::make_pair(2, 4));
    graph[1].push_back(std::make_pair(2, 1));
    graph[1].push_back(std::make_pair(3, 7));
    graph[2].push_back(std::make_pair(4, 3));
    graph[3].push_back(std::make_pair(4, 2));
    graph[3].push_back(std::make_pair(5, 1));
    graph[4].push_back(std::make_pair(5, 5));

    int start = 0;  // 起始节点
    std::vector<int> dist(n, INF);  // 保存起始节点到各个节点的最短路径长度

    dijkstra(graph, start, dist);

    std::cout << "Shortest distances from node " << start << ":\n";
    for (int i = 0; i < n; i++) {
    
    
        std::cout << "Node " << i << "'s shortest distance: " << dist[i] << std::endl;
    }

    return 0;
}

Bellman-Ford algorithm

The Bellman-Ford algorithm is used to solve the single-source shortest path problem for graphs containing negative-weighted edges. It gradually updates the shortest path length from the starting node to each node by performing relaxation operations on all edges.

#include <iostream>
#include <vector>
#include <limits>

#define INF std::numeric_limits<int>::max()

struct Edge {
    
    
    int src, dest, weight;
};

void bellmanFord(std::vector<Edge>& edges, int n, int start, std::vector<int>& dist) {
    
    
    dist[start] = 0;

    for (int i = 1; i <= n - 1; i++) {
    
    
        for (const auto& edge : edges) {
    
    
            int u = edge.src;
            int v = edge.dest;
            int weight = edge.weight;

            if (dist[u] != INF && dist[u] + weight < dist[v]) {
    
    
                dist[v] = dist[u] + weight;
            }
        }
    }

    // 检查是否存在负权环
    for (const auto& edge : edges) {
    
    
        int u = edge.src;
        int v = edge.dest;
        int weight = edge.weight;

        if (dist[u] != INF && dist[u] + weight < dist[v]) {
    
    
            std::cout << "Graph contains a negative-weight cycle" << std::endl;
            return;
        }
    }
}

int main() {
    
    
    int n = 5;  // 节点数
    std::vector<Edge> edges = {
    
    
        {
    
    0, 1, -1},
        {
    
    0, 2, 4},
        {
    
    1, 2, 3},
        {
    
    1, 3, 2},
        {
    
    1, 4, 2},
        {
    
    3, 2, 5},
        {
    
    3, 1, 1},
        {
    
    4, 3, -3},
    };

    int start = 0;  // 起始节点
    std::vector<int> dist(n, INF);  // 保存起始节点到各个节点的最短路径长度

    bellmanFord(edges, n, start, dist);

    std::cout << "Shortest distances from node " << start << ":\n";
    for (int i = 0; i < n; i++) {
    
    
        std::cout << "Node " << i << "'s shortest distance: " << dist[i] << std::endl;
    }

    return 0;
}

Minimum Spanning Tree Algorithm

Prim's algorithm and Kruskal's algorithm

Prim's algorithm

Prim's algorithm is an algorithm used to solve the minimum spanning tree of weighted undirected graphs. It starts from a starting node and gradually expands the edges of the minimum spanning tree until all nodes are included. The core idea of ​​the algorithm is a greedy strategy, each time selecting a minimum weight edge connecting the selected node and the unselected node from the current minimum spanning tree.

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

#define INF std::numeric_limits<int>::max()

struct Edge {
    
    
    int src, dest, weight;
};

struct Compare {
    
    
    bool operator()(const Edge& e1, const Edge& e2) {
    
    
        return e1.weight > e2.weight;
    }
};

std::vector<Edge> primMST(std::vector<std::vector<std::pair<int, int>>>& graph) {
    
    
    int n = graph.size();
    std::vector<Edge> mst;  // 保存最小生成树的边
    std::vector<bool> visited(n, false);
    std::priority_queue<Edge, std::vector<Edge>, Compare> pq;

    // 从节点0开始
    visited[0] = true;
    for (const auto& neighbor : graph[0]) {
    
    
        int v = neighbor.first;
        int weight = neighbor.second;
        pq.push({
    
    0, v, weight});
    }

    while (!pq.empty()) {
    
    
        Edge e = pq.top();
        pq.pop();

        int src = e.src;
        int dest = e.dest;
        int weight = e.weight;

        if (visited[dest]) {
    
    
            continue;
        }

        visited[dest] = true;
        mst.push_back({
    
    src, dest, weight});

        for (const auto& neighbor : graph[dest]) {
    
    
            int v = neighbor.first;
            int w = neighbor.second;
            if (!visited[v]) {
    
    
                pq.push({
    
    dest, v, w});
            }
        }
    }

    return mst;
}

int main() {
    
    
    int n = 5;  // 节点数
    std::vector<std::vector<std::pair<int, int>>> graph(n);  // 邻接表表示的图

    // 添加边
    graph[0].push_back(std::make_pair(1, 2));
    graph[1].push_back(std::make_pair(0, 2));

    graph[0].push_back(std::make_pair(2, 3));
    graph[2].push_back(std::make_pair(0, 3));

    graph[1].push_back(std::make_pair(2, 4));
    graph[2].push_back(std::make_pair(1, 4));

    graph[1].push_back(std::make_pair(3, 3));
    graph[3].push_back(std::make_pair(1, 3));

    graph[1].push_back(std::make_pair(4, 1));
    graph[4].push_back(std::make_pair(1, 1));

    graph[2].push_back(std::make_pair(3, 2));
    graph[3].push_back(std::make_pair(2, 2));

    graph[3].push_back(std::make_pair(4, 5));
    graph[4].push_back(std::make_pair(3, 5));

    std::vector<Edge> mst = primMST(graph);

    std::cout << "Minimum Spanning Tree:\n";
    for (const auto& edge : mst) {
    
    
        std::cout << edge.src << " - " << edge.dest << " : " << edge.weight << std::endl;
    }

    return 0;
}

Kruskal algorithm

Kruskal's algorithm is an algorithm for solving the minimum spanning tree of weighted undirected graphs. It selects edges in ascending order of edge weights and determines whether a cycle will be formed. If a cycle will not be formed, the edge is added to the minimum spanning tree. The basic idea of ​​the algorithm is a greedy strategy, selecting the edge with the smallest weight each time until all nodes are included in the spanning tree.

#include <iostream>
#include <vector>
#include <algorithm>

struct Edge {
    
    
    int src, dest, weight;
};

struct UnionFind {
    
    
    std::vector<int> parent;
    std::vector<int> rank;

    UnionFind(int n) {
    
    
        parent.resize(n);
        rank.resize(n, 0);

        for (int i = 0; i < n; i++) {
    
    
            parent[i] = i;
        }
    }

    int find(int x) {
    
    
        if (parent[x] != x) {
    
    
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    void unite(int x, int y) {
    
    
        int rootX = find(x);
        int rootY = find(y);

        if (rootX != rootY) {
    
    
            if (rank[rootX] < rank[rootY]) {
    
    
                parent[rootX] = rootY;
            } else if (rank[rootX] > rank[rootY]) {
    
    
                parent[rootY] = rootX;
            } else {
    
    
                parent[rootY] = rootX;
                rank[rootX]++;
            }
        }
    }
};

bool compareEdges(const Edge& e1, const Edge& e2) {
    
    
    return e1.weight < e2.weight;
}

std::vector<Edge> kruskalMST(std::vector<Edge>& edges, int n) {
    
    
    std::vector<Edge> mst;  // 保存最小生成树的边
    UnionFind uf(n);

    std::sort(edges.begin(), edges.end(), compareEdges);

    for (const auto& edge : edges) {
    
    
        int src = edge.src;
        int dest = edge.dest;

        if (uf.find(src) != uf.find(dest)) {
    
    
            mst.push_back(edge);
            uf.unite(src, dest);
        }
    }

    return mst;
}

int main() {
    
    
    int n = 5;  // 节点数
    std::vector<Edge> edges = {
    
    
        {
    
    0, 1, 2},
        {
    
    0, 2, 3},
        {
    
    1, 2, 4},
        {
    
    1, 3, 3},
        {
    
    1, 4, 1},
        {
    
    2, 3, 2},
        {
    
    3, 4, 5},
    };

    std::vector<Edge> mst = kruskalMST(edges, n);

    std::cout << "Minimum Spanning Tree:\n";
    for (const auto& edge : mst) {
    
    
        std::cout << edge.src << " - " << edge.dest << " : " << edge.weight << std::endl;
    }

    return 0;
}

Article summary

These algorithms are very useful when solving graph theory problems, and the appropriate algorithm can be selected based on the requirements of the problem and the characteristics of the graph. It should be noted that in practical applications, the algorithm may need to be appropriately adjusted and optimized according to specific problems to meet the needs of the problem.

Guess you like

Origin blog.csdn.net/qq_45902301/article/details/131650181