最小生成树——普利姆 克鲁斯卡尔

最小生成树

生成树定义:是原图的一个极小连通子图,含有原图的全部顶点,但只有n-1条边。它连通但边只有n-1,也就是说任意让两点连边必定成环,不过这结论好像没啥用。

最小生成树:对于一张图的生成树可能有多种,对于边权和最小的一种就是最小生成树了。

 

prim算法

 

首先首先,我们来几个标识,原图是N={V,E},TE是N上最小生成树的边集,然后有个已选点集合U,那么未选点集就是V-U了。

首先,先来个起点,加入到U里面。

然后,在所以边(a,b),a属于U,b属于V-U里面,找一条权值最小的边(a0,b0),并入集合TE,同时b0加入到U。

 

最终,经过不断的重复,U越来越多,达到n个点,也就是说U=V;

 

证明:    设prim生成的图为G0

    假设存在Gmin使得cost(Gmin)<cost(G0),则Gmin存在边<u,v>不属于G0;

    将<u,v>加入Gmink可以得到一个环,且<u,v>不是该环的最长边

    这与prim每次生成最短边矛盾;

prim的实现:

  首先会用到closedge结构体数组,包括adjvex域和lowcost域;

 

struct node{
    int adjvex;
    int lowcost;
}closedge[maxn];   //closedge[i]中,i和closedge[i].adjvex组成一条边,权值为lowcost
//整个数组用于记录U到V-U具最小权值的边,每次选最小的就是上面的步骤二了

 先来看看算法分析:

struct node{
    int adjvex;
    int lowcost;//一开始初始化为正无穷
}closedge[maxn];   //closedge[i]中,i和closedge[i].adjvex组成一条边,权值为lowcost
//整个数组用于记录U到V-U具最小权值的边,每次选最小的就是上面的步骤二了

void prim(AMGRAPH G, char u)
{//以名字u为起点,在邻接矩阵表示的图G中,构建最小生成树
    int k=locate(G,u);//寻找u名字在定点表中的下标
    for(int i=0;i<n;i++)
        if(i!=k) closedge[i]={u,G.arc[k][j]};//对起点的邻接点初始化
    closedge[k].lowcost =0;//为0就代表着选过了
    for(int i=1;i<G.vexnum;i++)
    {//选择其余n-1个点
        k=min(closedge);//找出closedge中lowcost最小的编号(下标)
        int u0=closedge[k].adjvex;//属于U
        int v0=G.vexs[k];         //属于V-U
        cout<<u0<<v0;
        closedge[k].lowcost =0;
        for(int j=0;j<G.vexnum;++j){
            if(G.arc[k][j]<closedge[j].lowcost )
                closedge[j].lowcost=G.arc[k][j];
                closedge[j].adjvex =G.vex[k];
        }
    }
}

正式代码:

最小生成树——普利姆  克鲁斯卡尔
 1 struct node{
 2     int adjvex; /*或者*/ char adjvex;//一个是编号一个是名字
 3     int lowcost;//一开始初始化为正无穷
 4 }closedge[maxn];   //closedge[i]中,i和closedge[i].adjvex组成一条边,权值为lowcost
 5 //整个数组用于记录U到V-U具最小权值的边,每次选最小的就是上面的步骤二了
 6 
 7 int locate(AMGRAPH G,char u)
 8 {
 9     for(int i=0;i<=n;i++)
10         if(G.vexs[i]==u)
11             return i;
12 }
13 
14 void prim(AMGRAPH G, char u)
15 {//以名字u为起点,在邻接矩阵表示的图G中,构建最小生成树
16 
17     int k=locate(G,u);//寻找u名字在定点表中的下标
18     for(int i=0;i<n;i++)
19         if(i!=k) closedge[i]={u,G.arc[k][j]};//对起点的邻接点初始化
20     closedge[k].lowcost =0;//为0就代表着选过了
21     
22     for(int i=1;i<G.vexnum;i++)
23     {//选择其余n-1个点
24     
25         int temp=INT-MAX,k;
26         for(int j=0;j<G.vexnum;j++)
27         {
28             if(closedge[j].lowcost<temp&&closedge[j].lowcost!=0)//对于未选的最小权值的点
29                 k=j;
30         }
31         
32         char u0=closedge[k].adjvex;//属于U
33         char v0=G.vexs[k];         //属于V-U
34         cout<<u0<<v0;
35         
36         closedge[k].lowcost =0;//标记k,代表选了
37         
38         for(int j=0;j<G.vexnum;++j){//刷新与k的邻接点,若权值比lowcost小,就刷新
39             if(G.arc[k][j]<closedge[j].lowcost )
40                 closedge[j].lowcost=G.arc[k][j];
41                 closedge[j].adjvex =G.vex[k];
42         }
43     }
44 }
View Code

 好啦,这算是彻底领悟了,那就看看更简单的克鲁斯卡尔算法;

克鲁斯卡尔

 与prim不同,kruskal是加边

与课本不同,这是另一种描述,很像哈夫曼树

 

克鲁斯卡尔算法:先构造一个只含n个顶点,但边集为空的子图,

        把子图中各个顶点看成各棵树的根节点,之后,

        从边集选权值最小的边,若该边的两个顶点属于不同的树

        就加进子图,把两棵树并成一颗,以此类推,直至森林中只有一棵树。

这个定义本质还是不断让两个连通分量变成一个,知道子图连通。

 

证明:这这这显而易见啊,不过还是要证的,网上的术语好烦,这里以后补。

 

那么如何判断一条边连的是不是两个不同的连通分量呢?

如果是同一连通分量,那么加入这条边就一定成环,我们可以判断它成不成环,

或者直接判断属不属于同一个连通分量,答案呼之欲出——并查集

代码:

 

最小生成树——普利姆  克鲁斯卡尔
 1 struct node{
 2     int u,v,w;
 3 }map[maxn];
 4 
 5 bool cmp(node a,node b)
 6 {
 7     return a.w <b.w ;
 8 }
 9 
10 int find(int x)
11 {
12     return x==p[x]?x:p[x]=find(p[x]);//并查集,一直找掌门,并将中间赋值
13 }
14 
15 void kelusikaer (node map[] ,int u)
16 {//对图map图,计算最小生成树的边权和
17     int p[maxn];
18     for(int i=0;i<n;i++)
19         p[i]=i;
20     /*这里输入数据*/
21     sort(map,map+k,cmp);//k为边的数目
22     int ans=0;
23     for(int i=0;i<k;i++)
24     {
25         int x=find(map[i].u );
26         int y=find(map[i].v );
27         if(x!=y){//如果不等,就代表不是同一个连通分量
28             ans+=map[i].w ;
29             p[x]=y;//代表合并了两个连通分量
30         }
31     }
32     cout<<ans<<endl;
33 }
View Code

 

 

 

  

 

 

 

 

 

    

 

上一篇:第六章学习小结


下一篇:最小生成树(Minimum Cost Spanning Tree)