最小生成树

一个连通图的生成树是一个极小的连通子图,包含图中全部顶点,但只有足以构成一棵树的n-1条边。于是,将构造连通网的最小代价生成树称为最小生成树(Minimum Cost Spanning Tree)。经典的算法有两种,普里姆算法和克鲁斯卡尔算法,下面详细介绍。

普里姆(Prim)算法
/*Prim算法生成最小生成树,邻接矩阵,时间复杂度O(n^2)*/
void MiniSpanTree(MGraph G){
   int min,i,j,k;
   int adjvex[MAXSIZE]; //保存相关顶点下标
   int lowcost[MAXSIZE];  //保存边的权值
   lowcost[0]=0;  //初始化,将v0加入生成树,下标为0的已加入树
   adjvex[0]=0;  //初始化第一个顶点下标为0
   /*读取邻接矩阵第一行数据,并将数组赋值给lowcost数组*/
   for(i=1;i<G.numVertexes;i++){  //循环除下标为0外的全部顶点
      lowcost[i]=G.arc[0][i];  //将v0顶点与之有边的权值存入数组
      adjvex[i]=0;  //初始化均为v0的下标
   }//此时,lowcost={0,10,00,00,00,11,00,00,00},adjvex均为0

   /*构造最小生成树过程*/
   for(i=1;i<G.numVertexes;i++){
      min=INFINITY;  //初始化最小值为无穷大
      j=1;k=0;  //j:顶点下标,循环变量;k:存储最小权值的顶点下标.
      /*不断修改min为lowcost数组中最小值,并用k保存该顶点下标*/
      while(j<G.numVertexes){  //循环全部顶点
         if(lowcost[j]!=0 && lowcost[j]<min){//权值不为0,且小于min
         //lowcost[j]!=0表示顶点不参与最小权值查找
            min=lowcost[j];  //取当前权值为最小值
            k=j;  //保存当前结点
         }
         j++;
      }//循环后,min=10,k=1;
      printf("(%d,%d)",arjvex[k],k); //(0,1),表示v0-v1为树的第一条边
      lowcost[k]=0; //k=1,也就是将v1纳入最小生成树中,此时lowcost={0,0,00,00,00,11,00,00,00},表示此顶点完成任务。

      for(j=1;j<G.numVertexes;j++){  //循环全部顶点
         if(lowcost[j]!=0 && G.arc[k][j]<lowcost[j]){  //k=1
         //将v1行的值和lowcost数组比较,18、16、12均比无穷大小,所以lowcost={0,0,18,00,00,11,16,00,12},lowcost={0,0,1,0,0,0,1,0,1},其中v1,v0不参与权值比较。
            lowcost[j]=G.arc[k][j];  
            adjvex[j]=k;
         }//min=11,k=5,adjvex[5]=0,表示v0-v5为最小生成树的第二条边
      }
   }//lowcost={0,0,18,00,26,0,16,00,12},lowcost={0,0,1,0,5,0,1,0,1}
}

首先将 v0 加入最小生成树, v0 行的邻接矩阵为 [0,10,,,,11,,,,] ,adjvex全部为0,lowcost等于其邻接矩阵,找到其中的最小值10,对应的顶点为 v1

v1 加入到最小生成树中,并将lowcost和 v1 这行的邻接矩阵进行比较,更小的加入到lowcost中,为0的不参与比较,所以lowcost= [0,0,18,,,11,16,,12] ,adjvex={0,0,1,0,0,0,1,0,1},发现当前的lowcost中的最小值为11,下标为5,对应的顶点为 v5

v5 加入到最小生成树中,并将lowcost和 v5 这行的邻接矩阵进行比较,更小的加入到lowcost中,为0的不参与比较,所以lowcost= [0,0,18,,26,0,16,,12] ,adjvex={0,0,1,0,5,0,1,0,1},发现当前的lowcost中的最小值为12,下标为8,对应的顶点为 v8

v8 加入到最小生成树中,并将lowcost和 v8 这行的邻接矩阵进行比较,更小的加入到lowcost中,为0的不参与比较,所以lowcost= [0,0,8,21,26,0,16,,0] ,adjvex={0,0,8,8,5,0,1,0,1},发现当前的lowcost中的最小值为8,下标为2,对应的顶点为 v2

v2 加入到最小生成树中,并将lowcost和 v2 这行的邻接矩阵进行比较,更小的加入到lowcost中,为0的不参与比较,所以lowcost= [0,0,0,21,26,0,16,,0] ,adjvex={0,0,8,8,5,0,1,0,1},发现当前的lowcost中的最小值为16,下标为6,对应的顶点为 v6

v6 加入到最小生成树中,并将lowcost和 v6 这行的邻接矩阵进行比较,更小的加入到lowcost中,为0的不参与比较,所以lowcost= [0,0,0,21,26,0,0,19,0] ,adjvex={0,0,8,8,5,0,1,6,1},发现当前的lowcost中的最小值为19,下标为7,对应的顶点为 v7

v7 加入到最小生成树中,并将lowcost和 v7 这行的邻接矩阵进行比较,更小的加入到lowcost中,为0的不参与比较,所以lowcost= [0,0,0,16,7,0,0,0,0] ,adjvex={0,0,8,7,7,0,1,6,1},发现当前的lowcost中的最小值为7,下标为4,对应的顶点为 v4

v4 加入到最小生成树中,并将lowcost和 v4 这行的邻接矩阵进行比较,更小的加入到lowcost中,为0的不参与比较,所以lowcost= [0,0,0,16,0,0,0,0,0] ,adjvex={0,0,8,7,7,0,1,6,1},发现当前的lowcost中的最小值为16,下标为3,对应的顶点为 v3

v3 加入到最小生成树中,并将lowcost和 v3 这行的邻接矩阵进行比较,更小的加入到lowcost中,为0的不参与比较,所以lowcost= [0,0,0,0,0,0,0,0,0] ,adjvex={0,0,8,7,7,0,1,6,1},完成。

Prim最小生成树

克鲁斯卡尔(Kruskal)算法

普里姆(Prim)算法是以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树。直接以边为目标构建,权值在边上,直接找最小权值的边来构建生成树,在构建时要考虑是否会形成环路,因此用到图的存储结构中的边集数组结构。

/*对边集数组Edge的定义*/
typedef struct{
   int begin;
   int end;
   int weight;
}Edge;

将图转变为对应的边集数组,并且将它们按权值由小到大排序,如下图所示:
这里写图片描述

数组beginendweight
edges[0]477
edges[1]288
edges[2]0110
edges[3]0511
edges[4]1812
edges[5]3716
edges[6]1616
edges[7]5617
edges[8]1218
edges[9]6719
edges[10]3420
edges[12]3821
edges[13]3624
edges[14]4526
/*Kruskal算法生成最小生成树,边集数组,时间复杂度O(eloge)*/
void MiniSpanTree_Kruskal(MGraph G){
   int i,n,m;
   Edge edges[MAXEDGE]; //定义边集数组,MAXEDGE边数极大值
   int parent[MAXVEX];  //判断边与边是否形成环路
   /*省略将邻接矩阵G转化为边集数组edges并按权由小到大排序的代码,形成edges一维数组*/
   for(i=0;i<G.numVertexes;i++)
      parent[i]=0;  //初始化数组值为0
    /*对边集数组进行遍历*/    
   for(i=0;i<G.numEdges;i++){  //循环每一条边
      n=Find(parent,edges[i].begin);
      m=Find(parent,edges[i].end);
      if(n != m){  //假如n和m不等,说明此边没有与现有生成树形成环路
         parent[n]=m;//将此边结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中
         printf("(%d,%d) %d",edges[i].begin,edges[i].end,edges[i].weight);       
      }
   }
}

int Find(int *parent,int f){ //查找连线顶点的尾部下标
   while(parent[f]>0) f=parent[f];
   return f;
}

开始i=0,查询边集数组得到( v4 , v7 ),此时parent中都为0,所以n=4,m=7,parent[4]=7,parent数组值为{0,0,0,0,7,0,0,0,0},将 ( v4 , v7 ) 纳入到最小生成树。

然后i=1,查询边集数组,edges[1]得到边( v2 , v8 ),n=2,m=8,parent[2]=8,parent数组值为{0,0,8,0,7,0,0,0,0},将 ( v2 , v8 ) 纳入到最小生成树。

然后i=2,查询边集数组,edges[2]得到边( v0 , v1 ),n=0,m=1,parent[0]=1,parent数组值为{1,0,8,0,7,0,0,0,0},将 ( v0 , v1 ) 纳入到最小生成树。

然后i=3,查询边集数组,edges[3]得到边( v0 , v5 ),由于parent[0]=1,所以n=1,m=5,parent数组值为{1,5,8,0,7,0,0,0,0},将 ( v0 , v5 ) 纳入到最小生成树。

然后i=4,查询边集数组,edges[4]得到边( v1 , v8 ),由于parent[1]=5,parent[5]=0,所以n=5,m=8,parent数组值为{1,5,8,0,7,8,0,0,0},将 ( v1 , v8 ) 纳入到最小生成树。

然后i=5,查询边集数组,edges[5]得到边( v3 , v7 ),由于parent[3]=0,parent[7]=0,所以n=3,m=7,parent数组值为{1,5,8,7,7,8,0,0,0},将 ( v3 , v7 ) 纳入到最小生成树。

然后i=6,查询边集数组,edges[6]得到边( v1 , v6 ),由于parent[1]=5,parent[5]=8,parent[8]=0所以n=8,m=6,parent数组值为{1,5,8,7,7,8,0,0,6},将 ( v1 , v6 ) 纳入到最小生成树。

然后i=7,查询边集数组,edges[7]得到边( v5 , v6 ),由于parent[5]=8,parent[8]=6,parent[6]=0所以n=6,m=6,m=n,所以退出循环,不添加该边。

然后i=8,查询边集数组,edges[8]得到边( v1 , v2 ),由于parent[1]=5,parent[5]=8,parent[8]=6,parent[6]=0,parent[2]=8所以n=6,m=6,m=n,所以退出循环,不添加该边。

然后i=9,查询边集数组,edges[9]得到边( v6 , v7 ),由于parent[6]=0,parent[7]=0,所以n=6,m=7,parent数组值为{1,5,8,7,7,8,7,0,6},将 ( v6 , v7 ) 纳入到最小生成树。

此后面的循环均会造成环路,最终最小生成树的构造过程如下:

这里写图片描述

小结:Kruskal算法针对边展开,边数少时效率很高,即对稀疏图有很大优势;Prim算法对于稠密图,即边数非常多时会更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值