• Kruskal算法(转)


    对于一个给定的连通的无向图 G = (V, E),希望找到一个无回路的子集 T,T 是 E 的子集,它连接了所有的顶点,且其权值之和为最小。

    因为 T 无回路且连接所有的顶点,所以它必然是一棵树,称为生成树(Spanning Tree),因为它生成了图 G。显然,由于树 T 连接了所有的顶点,所以树 T 有 V – 1 条边。一张图 G 可以有很多棵生成树,而把确定权值最小的树 T 的问题称为最小生成树问题(Minimum Spanning Tree)。术语 “最小生成树” 实际上是 “最小权值生成树” 的缩写。

    Kruskal 算法提供一种在 O(ElogV) 运行时间确定最小生成树的方案。Kruskal 算法基于贪心算法(Greedy Algorithm)的思想进行设计,其选择的贪心策略就是,每次都选择权重最小的但未形成环路的边加入到生成树中。其算法结构如下:

    1. 将所有的边按照权重非递减排序;
    2. 选择最小权重的边,判断是否其在当前的生成树中形成了一个环路。如果环路没有形成,则将该边加入树中,否则放弃。
    3. 重复步骤 2,直到有 V – 1 条边在生成树中。

    上述步骤 2 中使用了 Union-Find 算法来判断是否存在环路。

    例如,下面是一个无向连通图 G。

    图 G 中包含 9 个顶点和 14 条边,所以期待的最小生成树应包含 (9 – 1) = 8 条边。

    首先对所有的边按照权重的非递减顺序排序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Weight Src Dest
    1 7 6
    2 8 2
    2 6 5
    4 0 1
    4 2 5
    6 8 6
    7 2 3
    7 7 8
    8 0 7
    8 1 2
    9 3 4
    10 5 4
    11 1 7
    14 3 5

    然后从排序后的列表中选择权重最小的边。

    1. 选择边 {7, 6},无环路形成,包含在生成树中。

    2. 选择边 {8, 2},无环路形成,包含在生成树中。

    3. 选择边 {6, 5},无环路形成,包含在生成树中。

    4. 选择边 {0, 1},无环路形成,包含在生成树中。

    5. 选择边 {2, 5},无环路形成,包含在生成树中。

    6. 选择边 {8, 6},有环路形成,放弃。

    7. 选择边 {2, 3},无环路形成,包含在生成树中。

    8. 选择边 {7, 8},有环路形成,放弃。

    9. 选择边 {0, 7},无环路形成,包含在生成树中。

    10. 选择边 {1, 2},有环路形成,放弃。

    11. 选择边 {3, 4},无环路形成,包含在生成树中。

    12. 由于当前生成树中已经包含 V – 1 条边,算法结束。

    #include<iostream>
    #include<string>
    #include<cstdio>
    #include<algorithm>
    #define MAX 1000
    using namespace std;
    
    int father[MAX];
    int son[MAX];
    int v, l;
    //瀛樺偍杈逛俊鎭
    struct Edge {
        int begin;
        int end;
        int weight;
    };
    
    bool cmp(const Edge &a, const Edge &b) {
        return a.weight < b.weight;
    }
    int unionsearch(int x) {
        return x == father[x] ? x : unionsearch(father[x]);
    }
    bool join(int x, int y) {
        int root1, root2;
        root1 = unionsearch(x);
        root2 = unionsearch(y);
        if(root1 == root2) {
            return false;
        } else if(son[root1] >= son[root2]) {
            father[root2] = root1;
            son[root1] += son[root2]; 
        } else {
            father[root1] = root2;
            son[root2] += son[root1];
        }
    
        return true;
    
    }
    int main()
    {
        int ncase = 0, ltotal = 0, sum = 0, flag = 0;
    
        Edge edge[MAX];
        scanf("%d", &ncase);
        while(ncase--) {
            scanf("%d%d", &v, &l);
            ltotal = 0; sum = 0; flag = 0;
            for(int i = 0; i < v; i++) {
                father[i] = i;
                son[i] = 1;
            }
            for(int i = 0; i < l; i++) {
                scanf("%d%d%d", &edge[i].begin, &edge[i].end, &edge[i].weight);
            }
            //鏉冨€肩敱灏忓埌澶ф帓鍒
            sort(edge, edge + l, cmp);
            for(int i = 0; i < l; i++) {
                if(join(edge[i].begin, edge[i].end)) {
                    ltotal++;
                    sum += edge[i].weight;
                    cout << edge[i].begin << "->" << edge[i].end <<endl;
                }
                if(ltotal == v - 1) {
                    flag = 1;
                    break;
                } 
            }
            if(flag) {
                printf("%d
    ", sum);
            } else {
                printf("data error
    ");
            }
        }
    
        return 0;
    }
    

      

  • 相关阅读:
    冒泡排序
    题6 字典转数组 修改 大美女
    题5数组 排序 修改 字典以字符串输出
    题4数组 删除 排序
    数组字典结合删除排序 描述器
    盘点世界上最怪异的编程语言: 这6种编程语言的出现,只是为了好玩?
    Linux“四”种软件包都安排上了!详细安装过程曝光
    又一名程序员被判刑了!百度92年程序员改当“审核员”,编写脚本违规“开绿灯”
    C++的三种继承方式:public,protected,private
    Linux下查询进程占用内存的5种方式!还有谁不会?
  • 原文地址:https://www.cnblogs.com/LyningCoder/p/4285927.html
Copyright © 2020-2023  润新知