贪心算法(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

该图包含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\n");
    for (i = 0; i < e; ++i)
        printf("%d -- %d == %d\n", 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) 可以看做是一样的。

时间: 2024-09-20 15:13:22

贪心算法(2)-Kruskal最小生成树的相关文章

[算法系列之二十七]Kruskal最小生成树算法

简介 求最小生成树一共有两种算法,一个是就是本文所说的Kruskal算法,另一个就是Prime算法.在详细讲解Kruskal最小生成树算法之前,让我们先回顾一下什么是最小生成树. 我们有一个带权值的图,我们要求找到一个所有生成树中具有最小权值的生成树.如下图所示,T是图G的生成树.但不是具有最小权值的生成树. 我们可以把他们想象成一组岛屿和连接它们的可能的桥梁.当然修桥是非常昂贵和费时的,所以我们必须要知道建设什么样的桥梁去连接各个岛.不过有一个重要的问题,建设这样一组连接所有岛屿的桥梁的最低价

【算法小总结】Prim算法与Kruskal算法探索

以前以为自己用的生成最小生成树的方法是Prim算法,今天自己拜读了<数据结构与算法分析>之后才知道自己有多愚蠢,原来以前一直用的是KrusKal算法...... 今天好好说道说道这两个算法: KrusKal算法用于稀疏图,贪心策略,连续的按照最小的权值选择边. Prim算法用于稠密图,也是贪心策略,每次取离小生成树最近的点作为生成树的心点,并入生成树内生成新的小生成树,知道所有节点均被纳入生成树后结束.   这两种方法均可得到点点之间路径最短的联通图.   例如这个例子:   用Prim算法的

【算法导论】贪心算法之活动安排问题

         对于许多最优化问题来说,采用动态规划来求解最优解有点大材小用了,只需要采用更简单有效的贪心算法就行了.贪心算法就是所做的每一步选择都是当前最佳的,通过局部最佳来寻求全局最佳解.就像砝码称重一样,总是优先选择大的砝码.          贪心算法对大多数优化问题来说能产生最优解,但也不一定总是这样的.能用贪心算法解的典型问题包括活动选择问题.最小生成树.最短路径问题等等.下面我们来讨论活动活动选择问题:           对于上面问题,贪心算法的思想就是:贪心选择使得剩下的.未

贪心算法

当一个问题具有最优子结构性质时,可用动态规划求解. 贪心算法总是作出当前看来是最好的选择,并不从整体最优上考虑,只是在某种意义上的局部最优选择. 有时,即时贪心找不到整体最优解,其结果也是最优解的近似解. 应用实例: 活动安排问题 最优装载问题 哈夫曼编码 单源最短路径 最小生成树 多机调度问题 贪心算法,通过一系列的选择来得到问题的解,每一个选择都是当前状态下的局部最好选择,即贪心选择. 1 贪心选择性质: 指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心来达到 贪心选择可以依赖于以

贪心算法的理解与实例应用

问题描述 贪心算法的理解与实例应用 对贪心算法的深刻理解,以及贪心算法的经典应用,对相应的实例进行分析 解决方案 哈夫曼树-贪心算法的应用实例strtotime 深入理解应用实例---------------------- 解决方案二: 可参考 http://blog.csdn.net/effective_coder/article/details/8736718

关于贪心算法的题目的一个问题

问题描述 关于贪心算法的题目的一个问题 OJ上的一道题Given Length and Sum of Digits 题目是 我写的答案是 代码链接是 http://codepad.org/LirbPkpG 在oj上提交后出现"Wrong answer on test 8" 这是因为错在哪里? 解决方案 一个贪心算法实例Dijkstra算法是解单源最短路径问题的一个贪心算法 解决方案二: 应该是说的,这道题答案是错误的,你没有在本地环境测试一下么,报错是啥内容,这个算法乍一看,看不出问题

算法——贪心算法

贪心算法 贪心算法通过一系列的选择来得到问题的解.它所做的每一个选择都是当前状态下局部的最好选择,即贪心选择. 贪心选择的一般特征:贪心选择性质和最优子结构性质. 贪心选择性质: 所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到.这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别.在动态规划算法中,每步所做的选择往往依赖于相关子问题的解.因而只有在解出相关子问题后,才能做出选择.而在贪心算法中,仅在当前状态下做出最好选择,即局部最优选择.

用java写装载问题(用贪心算法和分支界限算法)

问题描述 用java写装载问题(用贪心算法和分支界限算法) 用java写算法中的装载问题,代码~(用分支限界算法和贪心算法写)... 解决方案 http://www.cnblogs.com/tianshuai11/archive/2012/05/04/2490852.html

1.4买书问题之贪心算法和动态规划

对于自己的白痴程度,自己已经快无法忍受了,到现在还不明白贪心算法和动态规划. 1.贪心算法 在对问题求解时,总是做出当前看来最好的选择,也就是说它不从整体最优上加以考虑,而是仅在局部考虑最优解. 虽然,它不能为所有问题提供最优解答,但是对广泛问题能产生整体最优解或近似解. 基本思路: 1.建立数序模型 2.把问题分解若干子问题,依次求解 3.把局部最优解合成原问题的一个解 2.动态规划 通过百度一下,从百度知道得到了一个很好的解答! 动态规划的基本思想就是把全局问题化为局部问题,为了全局优化必须