Kruskal algorithm, Kruskal reconstruction tree, LCA algorithm

K r u s k a l Kruskal Kruskal Arithmetic

K r u s k a l KruskalKruskal The algorithm is a greedy algorithm for solving the minimum spanning tree. Its basic idea is to select edges sequentially from the edge set in the graph so that the selected edges do not form a loop and satisfy the minimum edge weight sum.

The specific implementation process is as follows:

  1. Sort all edges in the original graph according to edge weight from small to large.

  2. Select the sorted edges in turn. If the two endpoints of this edge are not in the same connected block, add the edge, merge the connected blocks between them into a new connected block, and add the edge to the minimum spanning tree. The edges are concentrated.

  3. Repeat the above steps until n − 1 n−1 n1 Article 边(其中 n n n represents the number of nodes in the original graph), and the edge set obtained at this time is the minimum spanning tree of the original graph.

K r u s k a l KruskalKruskal Arithmetic time increase O ( m l o g m ) O(mlogm) O(mlo gm), of which m m m represents the number of sides of the original graph. Its advantage is that it is simple and easy to implement, and can handle the situation of weighted graphs and negatively weighted edges. However, it cannot handle graphs with self-loops and multiple edges.

#include <bits/stdc++.h>
using namespace std;

struct Edge {
    
    
    int u, v, w;
    Edge(int _u, int _v, int _w): u(_u), v(_v), w(_w) {
    
    }
    bool operator<(const Edge& other) const {
    
    
        return w < other.w;
    }
};

vector<int> parent, rank;

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

void unite(int pu, int pv) {
    
    
    if (rank[pu] < rank[pv]) parent[pu] = pv;
    else if (rank[pu] > rank[pv]) parent[pv] = pu;
    else {
    
    
        parent[pu] = pv;
        rank[pv]++;
    }
}

vector<Edge> kruskal(int n, vector<Edge>& edges) {
    
    
    // 初始化并查集
    parent.resize(n);
    rank.resize(n);
    for (int i = 0; i < n; i++) {
    
    
        parent[i] = i;
        rank[i] = 0;
    }

    // 将边按照边权从小到大排序
    sort(edges.begin(), edges.end());

    // 初始化最小生成树和已经加入最小生成树的边数
    vector<Edge> mst;
    int cnt = 0;

    for (const auto& e : edges) {
    
    
        // 判断两个节点是否在同一个连通块中
        int pu = find(e.u);
        int pv = find(e.v);
        if (pu == pv) continue;

        // 如果不在同一个连通块中,将它们合并成一个新的连通块,并将边加入最小生成树
        unite(pu, pv);
        mst.push_back(e);
        cnt++;

        // 如果已经加入了 n-1 条边,停止算法
        if (cnt == n - 1) break;
    }
    return mst;
}

int main() {
    
    
    int n = 6;
    vector<Edge> edges = {
    
     Edge(0, 1, 1), Edge(0, 2, 5), Edge(1, 2, 2), Edge(1, 3, 8),
                           Edge(2, 3, 3), Edge(1, 4, 3), Edge(2, 4, 6), Edge(3, 4, 7),
                           Edge(3, 5, 4), Edge(4, 5, 2) };
    vector<Edge> mst = kruskal(n, edges);
    for (const auto& e : mst) cout << e.u << " " << e.v << " " << e.w << "\n";
}

This algorithm is also a good application of union and search sets.帮助我更好理解并查集

The main scenarios of this algorithm are:Turn the graph into a tree, Require the minimum edge weight< /span>

K r u s k a l KruskalKruskal重全树

类似 K r u s k a l Kruskal Kruskal Calculation method:For reference

#include <bits/stdc++.h>
using namespace std;
struct Edge {
    
    
    int u, v, w; // 边的两个端点和边权
    Edge(int _u, int _v, int _w): u(_u), v(_v), w(_w) {
    
    }
    bool operator<(const Edge& other) const {
    
    
        return w < other.w;
    }
};

// 并查集的 find 操作
int find(int i, vector<int>& parent) {
    
    
    if (parent[i] != i) parent[i] = find(parent[i], parent);
    return parent[i];
}

// 并查集的 unite 操作
void unite(int pu, int pv, vector<int>& parent, vector<int>& rank) {
    
    
    if (rank[pu] < rank[pv]) parent[pu] = pv;
    else if (rank[pu] > rank[pv]) parent[pv] = pu;
    else {
    
    
        parent[pu] = pv;
        rank[pv]++;
    }
}

// 构建 Kruskal 重构树
void kruskal_reconstruction_tree(int n, vector<Edge>& edges, vector<vector<Edge>>& tree) {
    
    
    // 初始化并查集和 Kruskal 重构树
    vector<int> parent(n), rank(n);
    for (int i = 0; i < n; i++) {
    
    
        parent[i] = i;
        rank[i] = 0;
    }
    tree.resize(n);

    // 将边按照边权从小到大排序
    sort(edges.begin(), edges.end());

    for (const auto& e : edges) {
    
    
        // 判断两个节点是否在同一个连通块中
        int pu = find(e.u, parent);
        int pv = find(e.v, parent);
        if (pu == pv) continue;

        // 如果不在同一个连通块中,将它们合并成一个新的连通块,同时在 Kruskal 重构树上连接一条边
        tree[e.u].push_back(e);
        tree[e.v].push_back({
    
    e.v, e.u, e.w}); // 双向边
        unite(pu, pv, parent, rank);
    }
}

int main() {
    
    
    int n = 6;
    vector<Edge> edges = {
    
     Edge(0, 1, 1), Edge(0, 2, 5), Edge(1, 2, 2), Edge(1, 3, 8),
                           Edge(2, 3, 3), Edge(1, 4, 3), Edge(2, 4, 6), Edge(3, 4, 7),
                           Edge(3, 5, 4), Edge(4, 5, 2) };
    vector<vector<Edge>> tree;
    kruskal_reconstruction_tree(n, edges, tree);
    for (int i = 0; i < n; i++) {
    
    
        cout << "Node " << i << ":";
        for (const auto& e : tree[i]) cout << " (" << e.v << "," << e.w << ")";
        cout << "\n";
    }
}

K r u s k a l KruskalKruskal The main scenario of reconstructing the tree:The longest edge on the shortest road between two points

Explanation of the code:

treeThe array is used to store the Kruskal reconstruction tree.

Specifically, tree is a list with n n n single element (among them n n n is the number of vertices in the original graph), and each element is a list that stores information about its adjacent vertices and edges in the connected block where the vertex is located. For example, in the Kruskal reconstruction tree, if vertex u connects vertex v and the edge weight is w, then . also contains an element , and tree[u] contains an element (v, w)tree[v](u, w)

When the kruskal_reconstruction_tree function ends, the returned tree list is the representation of the Kruskal reconstruction tree. We can traverse the tree list, and for each node u, visit its adjacent nodes v and the corresponding edge weights a>, thus getting Kruskal reconstruction tree. and between w, and then establish an undirected edge with edge weight uvw

The result of the code should be:

Node 0: (1,1) (2,5)
Node 1: (0,1) (2,2) (4,3) (3,8)
Node 2: (0,5) (1,2) (4,6) (3,3)
Node 3: (2,3) (1,8) (5,4) (4,7)
Node 4: (3,7) (1,3) (2,6) (5,2)
Node 5: (4,2) (3,4)

L C A LCA LCAArithmetic

double L C A LCA LCA

The following is a C++ code implementation of the LCA algorithm based on doubling, assuming that the node number of the tree is from 0 0 0 to n − 1 n-1 n1, inside n n n is the number of nodes, and the number of the root node is 0 0 0

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e5+10;  // 最大节点数
const int LOGN = 20;      // log2(MAXN)

int n;                   // 节点数
vector<int> adj[MAXN];   // 无根树的邻接表表示
int depth[MAXN];         // 节点深度
int parent[MAXN][LOGN];  // parent[i][j] 表示节点 i 的 2^j 级祖先

void dfs(int u, int p, int d) {
    
    
    depth[u] = d;
    parent[u][0] = p;
    for (int i = 1; i < LOGN; ++i) parent[u][i] = parent[parent[u][i-1]][i-1];
    for (int v : adj[u]) {
    
    
        if (v != p)  dfs(v, u, d+1);
    }
}

// 计算节点 u 和节点 v 的 LCA
int lca(int u, int v) {
    
    
    // 使节点 u 和节点 v 处于同一深度
    if (depth[u] < depth[v]) {
    
    
        swap(u, v);
    }
    // 将节点 u 抬升到与节点 v 同一深度
    for (int i = LOGN-1; i >= 0; --i) {
    
    
        if (depth[parent[u][i]] >= depth[v]) {
    
    
            u = parent[u][i];
        }
    }
    // 如果节点 u 已经是节点 v 的祖先,则此时 u 就是它们的 LCA
    if (u == v) {
    
    
        return u;
    }
    // 同时抬升节点 u 和节点 v,直到它们的父节点相同
    for (int i = LOGN-1; i >= 0; --i) {
    
    
        if (parent[u][i] != parent[v][i]) {
    
    
            u = parent[u][i];
            v = parent[v][i];
        }
    }
    // 此时 parent[u][0](或 parent[v][0])就是它们的 LCA
    return parent[u][0];
}

Among them,dfs function is used to solve the depth and ancestor information of each node. When node u is accessed recursively, its depth is first set to d, and its parent node p is recorded. Then, for i = 1 i=1 i=1 log ⁡ 2 ( n ) \log_2(n) log2(n), calculation point u 2 i 2^i 2i level ancestor, that is, parent[u][i]=parent[parent[u][i-1]][i-1]. Finally, for node u's adjacent node v, if v is not its parent node, call dfs(v, u, d+1) Calculate the depth and ancestry information of nodev.

lcaThe function calculates the LCA of node u and node v. First, lift nodeu and nodev to the same depth, specifically if nodeu has a smaller depth than node i i v, then exchange their values; then enumerate from large to small i, as a result u 目次 2 i 2^i 2The depth of the i level ancestor is not less than the nodev, then the node u is raised to its level ancestor. a> 2 i 2^i 2i level ancestor. At this time, if node u is the ancestor of node v, then node u is their LCA, and returns u. Otherwise, enumerate from large to small i i i, simultaneously raising node u and node v until their parents are the same. At this time, parent[u][0] (or parent[v][0]) is their LCA, just return.

Guess you like

Origin blog.csdn.net/qq_61786525/article/details/130583787