图论 —— 生成树 —— 最小生成树 —— Kruskal

【基本思想】

Kruskal 算法基本思想是并查集思想,将所有边升序排序,并认为每一个点都是孤立的,分属 n 个独立的集合。

按顺序枚举每一条边,如果这条边连接的两个点分属两个不同的集合,那么就将这条边加入最小生成树,这两个不同的集合合并为一个集合;如果这条边连接的两个点属于同一集合,那么就跳过。直到选取 n-1条边为止(只剩一个集合)。

其时间复杂度为:O(E*logE),E代表边数。

【算法分析】

以下图为例

开始时,5 个集合{{1},{2},{3},{4},{5}},生成树中没有边,MST=0。

图论 —— 生成树 —— 最小生成树 —— Kruskal_第1张图片

第一次选择 <1,2> 这条边,将边加入生成树中,将两个顶点 1、2 合并为一个集合。

此时,有 4 个集合{{1,2},{3},{4},{5}},1 条边{<1,2>},MST=2。

图论 —— 生成树 —— 最小生成树 —— Kruskal_第2张图片

第二次选择的是 <4,5> 这条边,将这条边加入生成树中,将两个顶点 4、5 合并为一个集合。

此时,有 3 个集合{{1,2},{3},{4,5}},2 条边{<1,2>,<4,5>},MST=5。

图论 —— 生成树 —— 最小生成树 —— Kruskal_第3张图片

第三次选择的是 <3,5> 这条边,将这条边加入生成树中,将它的两个顶点 3、5 所在的两个集合合并为一个集合。

此时,有 2 个集合{{1,2},{3,4,5}},3 条边{<1,2>,<4,5>,<3,5>},MST=11。

图论 —— 生成树 —— 最小生成树 —— Kruskal_第4张图片

第四次选择的是 <2,5> 这条边,将这条边加入到生成树中,将它的两个顶点 2、5 所在的两个集合合并为一个集合。

此时,有 1 个集合{1,2,3,4,5},4 条边{<1,2>,<4,5>,<3,5>,<2,5>},MST=19。

图论 —— 生成树 —— 最小生成树 —— Kruskal_第5张图片

【算法描述】

struct Edge {
    int x, y;
    int dis;
    bool operator<(Edge K) const { return dis < K.dis; }
} edge[N];
int father[N];
int Find(int x) {
    if (father[x] == x)
        return x;
    return father[x] = Find(father[x]);
}
int kruskal(int n, int m) {
    for (int i = 1; i <= n; i++) //并查集初始化
        father[i] = i;
    sort(edge + 1, edge + m + 1); //升序排序

    int MST = 0;
    int edgeNum = 0; //边数
    for (int i = 1; i <= m; i++) {
        int x = Find(edge[i].x);
        int y = Find(edge[i].y);
        if (x != y) {
            father[x] = y;
            MST += edge[i].dis;
            edgeNum++;
        }
        if (edgeNum == n - 1) { // n-1条边时停止
            return MST;
        }
    }
}
int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i)
        scanf("%d%d%d", &edge[i].x, &edge[i].y, &edge[i].dis);
    int MST = kruskal(n, m);
    printf("%d\n", MST);
    return 0;
}

你可能感兴趣的:(#,图论——生成树)