zoukankan      html  css  js  c++  java
  • 贪心算法(2)-Kruskal最小生成树

    什么是最小生成树?

    生成树是相对图来说的,一个图的生成树是一个树并把图的所有顶点连接在一起。一个图可以有许多不同的生成树。一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树其实是最小权重生成树的简称。生成树的权重是考虑到了生成树的每条边的权重的总和。

    最小生成树有几条边?

    最小生成树有(V – 1)条边,其中V是给定的图的顶点数量。

    Kruskal算法

    下面是步骤寻找MST使用Kruskal算法

    1 1,按照所有边的权重排序(从小到大)
    2  
    3 2,选择最小的边。检查它是否形成与当前生成树形成环。如果没有形成环,讲这条边加入生成树。否则,丢弃它。 
    4  
    5 3,重复第2步,直到有生成树(V-1)条边

    步骤2使用并查集算法来检测环。如果不熟悉并查集建议阅读下并查集

    该算法是一种贪心算法。贪心的选择是选择最小的权重的边,并不会和当前的生成树形成环。让我们了解一个例子,考虑下面输入图

    spanning-tree-mst

    spanning-tree-mst

    该图包含9个顶点和14个边。因此,形成最小生成树将有(9 – 1)= 8条边。

    01 排序后:
    02 Weight   Src    Dest
    03 1         7      6
    04 2         8      2
    05 2         6      5
    06 4         0      1
    07 4         2      5
    08 6         8      6
    09 7         2      3
    10 7         7      8
    11 8         0      7
    12 8         1      2
    13 9         3      4
    14 10        5      4
    15 11        1      7
    16 14        3      5

    现在从已经排序的边中逐个选择
    1. edge 7-6:没有环,加入

    2. edge 8-2: 没有环,加入

    3. edge 6-5: 没有环,加入

    4. edge 0-1: 没有环,加入

    5. edge 2-5: 没有环,加入

    6. edge 8-6: 加入后会形成环,舍弃

    7. edge 2-3: 没有环,加入

    8. edge 7-8: 加入后会形成环,舍弃

    9. edge 0-7: 没有环,加入

    10. edge 1-2: 加入后会形成环,舍弃

    11. edge 3-4: 没有环,加入

    目前为止一家有了 V-1 条边,可以肯定V个顶点都一包含在内,到此结束。

    代码实现:

    // Kruskal 最小生成树算法
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    // 带有权重的边
    struct Edge
    {
        int src, dest, weight;
    };
    
    // 无向图
    struct Graph
    {
        // V-> 顶点个数, E->边的个数
        int V, E;
        // 由于是无向图,从 src 到 dest的边,同时也是 dest到src的边,按一条边计算
        struct Edge* edge;
    };
    
    //构建一个V个顶点 E条边的图
    struct Graph* createGraph(int V, int E)
    {
        struct Graph* graph = (struct Graph*) malloc( sizeof(struct Graph) );
        graph->V = V;
        graph->E = E;
        graph->edge = (struct Edge*) malloc( graph->E * sizeof( struct Edge ) );
        return graph;
    }
    
    //并查集的结构体
    struct subset
    {
        int parent;
        int rank;
    };
    
    // 使用路径压缩查找元素i
    int find(struct subset subsets[], int i)
    {
        if (subsets[i].parent != i)
            subsets[i].parent = find(subsets, subsets[i].parent);
    
        return subsets[i].parent;
    }
    
    // 按秩合并 x,y
    void Union(struct subset subsets[], int x, int y)
    {
        int xroot = find(subsets, x);
        int yroot = find(subsets, y);
        if (subsets[xroot].rank < subsets[yroot].rank)
            subsets[xroot].parent = yroot;
        else if (subsets[xroot].rank > subsets[yroot].rank)
            subsets[yroot].parent = xroot;
        else
        {
            subsets[yroot].parent = xroot;
            subsets[xroot].rank++;
        }
    }
    
    // 很据权重比较两条边
    int myComp(const void* a, const void* b)
    {
        struct Edge* a1 = (struct Edge*)a;
        struct Edge* b1 = (struct Edge*)b;
        return a1->weight > b1->weight;
    }
    
    // Kruskal 算法
    void KruskalMST(struct Graph* graph)
    {
        int V = graph->V;
        struct Edge result[V];  //存储结果
        int e = 0;  //result[] 的index
        int i = 0;  // 已排序的边的 index
    
        //第一步排序
        qsort(graph->edge, graph->E, sizeof(graph->edge[0]), myComp);
    
        // 为并查集分配内存
        struct subset *subsets =
            (struct subset*) malloc( V * sizeof(struct subset) );
    
        // 初始化并查集
        for (int v = 0; v < V; ++v)
        {
            subsets[v].parent = v;
            subsets[v].rank = 0;
        }
    
        // 边的数量到V-1结束
        while (e < V - 1)
        {
            // Step 2: 先选最小权重的边
            struct Edge next_edge = graph->edge[i++];
    
            int x = find(subsets, next_edge.src);
            int y = find(subsets, next_edge.dest);
    
            // 如果此边不会引起环
            if (x != y)
            {
                result[e++] = next_edge;
                Union(subsets, x, y);
            }
            // 否则丢弃,继续
        }
    
        // 打印result[]
        printf("Following are the edges in the constructed MST
    ");
        for (i = 0; i < e; ++i)
            printf("%d -- %d == %d
    ", result[i].src, result[i].dest,
                                                       result[i].weight);
        return;
    }
    
    // 测试
    int main()
    {
        /* 创建下面的图:
                 10
            0--------1
            |       |
           6|   5   |15
            |       |
            2--------3
                4       */
        int V = 4;  // 顶点个数
        int E = 5;  //边的个数
        struct Graph* graph = createGraph(V, E);
        // 添加边 0-1
        graph->edge[0].src = 0;
        graph->edge[0].dest = 1;
        graph->edge[0].weight = 10;
    
        graph->edge[1].src = 0;
        graph->edge[1].dest = 2;
        graph->edge[1].weight = 6;
    
        graph->edge[2].src = 0;
        graph->edge[2].dest = 3;
        graph->edge[2].weight = 5;
    
        graph->edge[3].src = 1;
        graph->edge[3].dest = 3;
        graph->edge[3].weight = 15;
    
        graph->edge[4].src = 2;
        graph->edge[4].dest = 3;
        graph->edge[4].weight = 4;
    
        KruskalMST(graph);
    
        return 0;
    }

    运行结果如下:

    Following are the edges in the constructed MST
    2 -- 3 == 4
    0 -- 3 == 5
    0 -- 1 == 10

     时间复杂度:

    O(ElogE) 或 O(ElogV)。 排序使用 O(ELogE) 的时间,之后我们遍历中使用并查集O(LogV) ,所以总共复杂度是 O(ELogE + ELogV)。E的值最多为V^2,所以

    O(LogV) 和 O(LogE) 可以看做是一样的。

  • 相关阅读:
    【CF617D】Roads in Yusland
    对偶问题
    【LG3722】[HNOI2017]影魔
    [HEOI2017] 相逢是问候
    [SHOI2009] 会场预约
    [SCOI2007] 修车
    [CTSC2008] 网络管理
    [国家集训队] 礼物
    [Poetize6] IncDec Sequence
    [网络流24题] 魔术球问题
  • 原文地址:https://www.cnblogs.com/wuchanming/p/3874065.html
Copyright © 2011-2022 走看看