最小生成树及其构造方法

ps:好久没写新文章了,来到了广州见习,没有网络,也不知道这学校怎么想的!!接下来进入正题

何谓最小生成树

对于一个带权连通无向图G中的不同生成树,各棵树的边上的权值之和可能不同,边上的权值之和最小的树称之为该图得最小生成树

构造方法

求图得最小生成树的俩个算法,即普利姆算法和克鲁斯卡尔算法

普利姆算法(Prim)

在了解普利姆算法之前,先想想我们最开始的目的是什么?俗气一点说就是画路线图,而且这个路线必须是最短的。

思路

先用抽象一点的语言描述,就是现在我们先拿出图中的第一个顶点(其实可以是任意顶点),然后把这个顶点放进集合A中,然后将这个顶点与集合外的顶点连成的线的权值做比较,把那个能连权值最小的线的顶点也放进集合里面。然后再把集合里面的俩个顶点与集合外的顶点继续比较,重复此步骤。可以参考:

第一步:从①开始,①进集合,用与集合外所有顶点能构成的边中找最小权值的一条边
①——②权6
①——③权1 -> 取①——③边
①——④权5

第二步:③进集合,①,③与②,④,⑤,⑥构成的最小边为
①——④权5
③——⑥权4 -> 取③——⑥边

第三步:⑥进集合,①,③,⑥与②,④,⑤构成的各最小边
①——②权6
③——②权5
⑥——④权2 -> 取⑥——④边

第四步:④进集合,①,③,⑥,④与②,⑤构成的各最小边
①——②权6
③——②权5 -> 取③——②边
⑥——⑤权6

第四步:②进集合,①,③,⑥,②,④与⑤构成的各最小边
②——⑤权3 -> 取②——⑤边

没错上面的说法很好,但实际中的代码跟上面的思想是一样,但就有点难理解,我们换一种说法,从代码的角度来分析。

假如我们现在拿到手的是一个邻接矩阵存储方法的图(假设这个图有n个顶点)

1.那么首先我们先实例化一个数组A,一个数组B。先这么规定,数组A存储的是n-1条边的权值。那么另外一个数组就是存储边的起始顶点。举个例子A[1]=3,B[1]=4,代表的就是第一个顶点所连接的最短的边的权值是3,然后这条最短边是和第4个顶点连接而成的。

2.那么有了这俩个数组,事情就变得好办很多了。B[i]=k代表的就是i与k连接可以形成最小权值的边,而A[i]数组就代表i顶点与这个k(k=B[i])连接的边的那个最小权值。这里有点绕口。

从实际例子来讲思路比较好,先看看下图:

3.先进行初始化

for(i=0;i<g.n;i++)
{
    lowcost[i] = g.edges[0][i];
    closest[i] = 0;
}

此时lowcost数组的意义就是各个顶点到0顶点的距离,closest数组的原理就是当前lowcost中的各个数值是由哪俩个顶点连起来的,可见初始化过程使得closest的每个值都为0,也就是lowcost中的每条边都是和0顶点连起来的.

4.接下来,取出lowcost中最小的边,如下:

min = INF;
//先遍历出lowcost里面最小的拿出来
for(j=0;j<g.n;j++)
{
    if(lowcost[j]!=0 && lowcost[j]<min)
    {
        min = lowcost[j];
        k = j;
    }
}
printf("边(%d,%d)权为:%d\n",closest[k],k,min);
lowcost[k] = 0;

取出的这条边就是顶点0与顶点k连接而成的。先用我们的脑子想一下,取出这条最短边之后我们应该做些什么事?

5.没错,取出第二条最短的边,我们需要把能和顶点0和顶点k连线的那些边取出来然后进行比较存储在lowcost数组中。

这也就跟我们第一个抽象化的思路一样,顶点0和顶点k在集合里面,与集合外面的顶点进行比较。

那么我们该怎么做这个比较的操作呢?

for(j=0;j<g.n;j++)
{
    if(g.edges[k][j]!=0 && lowcost[j]>g.edges[k][j])
    {5
        lowcost[j] = g.edges[k][j];
        closest[j] = k;
    }
}

g.edges[k][j]就是顶点j到顶点k的距离,lowcost[j]就是顶点j到顶点0的目前最短距离(在第一次大循环中,由于closest[j]都等于0,所以都是与顶点0的距离),拿这俩个数值做比较,得出一个更小的距离存储到lowcost中。

那么整个算法就是:

代码

#define INF 32767;
void Prim(MGraph g,int v)
{
    int lowcost[MAXV];
    int min;
    //就是对应的元素下标的顶点的连接的上一个顶点
    int closest[MAXV];
    int i,k;
    for(i=0;i<g.n;i++)
    {
        lowcost[i] = g.edges[v][i];
        closest[i] = v;
    }
    for(i=1;i<g.n;i++)
    {
        min = INF;
        //先遍历出lowcost里面最小的拿出来
        for(j=0;j<g.n;j++)
        {
            if(lowcost[j]!=0 && lowcost[j]<min)
            {
                min = lowcost[j];
                k = j;
            }
        }
        printf("边(%d,%d)权为:%d\n",closest[k],k,min);
        lowcost[k] = 0;
        //接下来做的就是把集合里的俩个顶点跟集合外的顶点组成的边的权值做比较
        for(j=0;j<g.n;j++)
        {
            if(g.edges[k][j]!=0 && lowcost[j]>g.edges[k][j])
            {
                lowcost[j] = g.edges[k][j];
                closest[j] = k;
            }
        }
    }
}

总结

对于普利姆算法来说,只要理解lowcost数组和closest数组,基本大概思路就已经清晰了

克鲁斯卡尔算法(Kruskal)

克鲁斯卡尔算法的思想就是先把所有边取出来,然后按从小到大排序,每次取出一条边,判断是否形成回路,如果是就丢弃,否则这条边就是我们想要的权值最小的边。

思路

核心-怎么样去判断回路

在这里需要简单地说一下思路。上面已经说了,我们有一个数组是按权值从小到大存储边的(这里先不要去考虑怎么存储边),然后我们每次拿出来一条边,这时候就需要判断这条边会不会跟我们前面已选择的边形成回路,因此,每次取出边的时候,我们需要把它的“原始顶点”存放到vset数组中,那么怎么存放呢?

假设我们现在有边ab,bc,cd都被取出来。那么这三条边就属于一个连通分量,也就是到时是连在一起的。那么ad这俩个顶点就不能连接起来了,很明显会构成一个回路,计算机应该怎么判断呢。可以这样规定,ab连在一起了,所以b的parent是a,bc连在一起了,所以c的parent是b,cd连在一起了所以d的parent是c。那么我们可以通过下面的代码来判断是否构成回路:

int Find(int *parent,int f)
{
    while(parnet[f]>0)
        f = parent[f]
    return f;
}

上面的代码实现了查找一个连通分量的其实顶点,通过上面的代码,我们可以知道d

的“原始顶点”是a,a的原始顶点也是a,所以相等,并不能连线。

代码

所以整个算法就是:

int Find(int *parent,int f)
{
    while(parnet[f]>0)
        f = parent[f]
    return f;
}

Typedef struct
{
    int begin;
    int end;
    int weight;
}Edge;

void Kruskal(MGraph g)
{
    Edge E[MaxSize];
    int vset[MaxSize];
    int i,k,j;
    for(i=0;i<g.n;i++)
        for(j=0;j<g.n;j++)
            if(g.edges[i][j]!=0 && g.edges[i][j]!=INF)
            {
                E[k].begin = i;
                E[k].end = j;
                E[k].weight = g.edges[i][j];
                k++;
            }
    InsertSort(E,g.e);//排序
    for(i=0;i<g.n;i++) vset[i]=0;
    k = 1;
    j = 0;
    while(k<g.n)
    {
        u1 = E[j].begin;
        u2 = E[j].end;
        sn1 = Find(vset,u1);
        sn2 = Find(vset,u2);
        if(sn1!=sn2)
        {
            vset[sn2] = sn1;
            printf();
            k++;
        }
        j++;
    }
}
时间: 2024-10-12 02:33:42

最小生成树及其构造方法的相关文章

学习日志---图之最小生成树算法

最小生成树: 从最小生成树的定义可知,构造有n个结点的无向连通带权图的最小生成树,必须满足以下三条: (1)构造的最小生成树必须包括n个结点: (2)构造的最小生成树中有且只有n-1条边: (3)构造的最小生成树中不存在回路. 构造最小生成树的方法有许多种,典型的构造方法有两种,一种称作普里姆(Prim)算法,另一种称作克鲁斯卡尔(Kruskal)算法. 特里姆算法 假设G=(V,E)为一个带权图,其中V为带权图中结点的集合,E为带权图中边的权值集合.设置两个新的集合U和T,其中U用于存放带权图

数据结构9——最小生成树

最小生成树:这里面有两个概念:(1):必须为一个树,并且为一棵生成树(树的定义有且仅有一个前驱结点,可以有有多个后驱子节点,并且(n-1)条边都在图中)        (2):必须是最小连通的.(多一条边会使树形成回路,不满足生成树的概念,少一条边,则会使树不连通) 因此,最小生成树必须满足三个条件: (1):必须包含n个结点 (2):必须包含(n-1) 条边 (3):不能构成回路. 最小生成树的两种构造方法:Prim算法,Kruskal算法. 1:Prim算法生成最小生成树 prim 算法的伪

次最小生成树 模版

次小生成树(转) 转载(http://www.cnblogs.com/z360/p/6875488.html) 所谓次小生成树,顾名思义就是从生成树中取出的第二小的生成树. 我们在前面已经说过最小生成树的概念及代码实现了,所以接下来要说的次小生成树应该比较简单理解了. 求次小生成树的两种方法 1:首先求出最小生成树T,然后枚举最小生成树上的边,计算除了枚举的当前最小生成树的边以外的所有边形成的最小生成树Ti,然后求最小的Ti就是次小生成树.2:首先计算出最小生成树T,然后对最小生成树上任意不相邻

HDU1863 畅通工程---(最小生成树)

畅通工程 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 27972    Accepted Submission(s): 12279 Problem Description 省政府"畅通工程"的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可).经过调查评估,得到的统计表中列出

构造方法中使用泛型

------------siwuxie095 构造方法中使用泛型: 构造方法可以为类中的属性初始化,如果类中的属性通过泛型指定,而又需要 通过构造方法设置属性内容的时候,构造方法的定义与之前并无不同,不需要 像声明类那样指定泛型 package com.siwuxie095.generic; class Context<T>{ private T value; public Context(T value) { this.value=value; } public T getValue() {

51Nod1601 完全图的最小生成树计数

传送门 我居然忘写题解啦!(记忆废) 不管怎么说,这题还算是一道好题啊--你觉得敦爷出的题会有水题么 -- 这题比较容易把人误导到Boruvka算法之类的东西上去(我们机房去刚D题的人一开始大多也被误导了),但仔细思考之后是可以发现问题的特殊性质的. 听说很多人是从Kruskal算法想到这道题的做法的?好吧我并不是,那我就写写我的思考过程好了-- 记得算导上有一道思考题,判断一个最小生成树算法的正确性.那个算法是这样的:把当前图的点集随意划分成两半,递归两半后选出连接两个点集的边中权值最小的一条

构造方法使用及注意事项

构造方法:构造方法是为类中的属性初始化的. 1构造方法的特征:与类名称相同.不含返回值类型的定义.不能在方法中使用return返回一个值 2通过构造方法为属性初始化的时候,最好也经过setter方法完成. eg: class Car{ private String a;//进行了封装 private int b; Car(String s,int n){ this.setA(s);//通过构造方法为属性初始化时,最好使用setter方法完成 this.setB(n); } public Stri

最小生成树求法 Prim + Kruskal

prim算法的思路 和dijkstra是一样的 每次选取一个最近的点 然后去向新的节点扩张 注意这里的扩张 不再是 以前求最短路时候的到新的节点的最短距离 而是因为要生成一棵树 所以是要连一根最短的连枝 所以关键部分修改一下 dist[u] = min(dist[u], e.cost) --->>e是连接 v 和 u的边 同样地 普同写法O(v^2) 用队列优化后O(E*logV) 1 #include <iostream> 2 #include <stdio.h> 3

构造方法

特点: 方法名称和类名系统 没有返回值类型,连void也没有 没有具体的返回值 作用: 对数据进行初始化的 注意事项: 构造方法也是有return语句的,格式是return; 如果我们没有给出构造方法,那么系统会提供一个无参的构造方法 如果我们给出了构造方法,那么系统就不会系统无参的构造方法