最小生成树的两种算法实现

1.问题

如何求一个包含有原图所有n个结点的且所有边的代价和最小的极小连通子图。

2.解析

构造最小生成树有两种算法模式。
第一种是Kruskal算法。它的要点就是选边。即从最短的边开始生成森林,最后连成树。简单的步骤:

1.图中的所有边按权重从小到大排序;
2.图中的n个顶点看成独立的n棵树组成的森林;
3.按权重从小到大选择边,所选的边连接的两个顶点应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
4.重复3,直到所有顶点都在一颗树内或者有n-1条边为止。

以下面为例。所有边按照权重排序为1.1.2.2.4.4.5.6.7.8.10,每一个顶点为独立的森林。然后一步步根据权重的顺序选择边。第一步选择v1和v4连接的边,权重为1。第二步选择v6和v7连接的边,权重为1。以此类推,等到选择第二条权重为4的边时,发现连接后会形成闭环或者说是两个顶点都被选中过,就不选择,跳到下一个边去。一直循环往复,直到所有顶点都在一颗树内或者有n-1条边为止。
最小生成树的两种算法实现_第1张图片

第二种是prim算法。它的要点就是选点。即从某一个点出发,在两个点之间选择权重最小的一条边,连接两个点,加入生成树之中。简单的步骤:

1.图的所有顶点集合为VV。
2.在顶点集合中能够组成的边中,选择一条代价最小的边,加入到最小生成树中。
3.重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。

以下图为例,先选择v1点,在v1的邻接点中选择权重最小的v4。然后再v1和v4的共同邻接点中继续选择权重最小的点,即v2或v3,这里选择了v2。然后在三者的并查集中再次选择,即v3。继续重复上述流程,直到最小生成树有n-1条边或者n个顶点为止。
最小生成树的两种算法实现_第2张图片

3.设计

1.1

void kruskal(edge e[],int m,int n){
	    int i,j,v1,v2,s1,s2,k,sum=0;
	    int set[m+1];
	    //构建顶点编号集
	    for(i=1;i<=m;i++){
	        set[i]=i;
	    }
	    //最小生成树的第k条边
	    k=1;
	    //边的下标
	    j=0;
	    while(k<=n){
	    	if (j>n) break;
	        //先获取j边的邻接点,以及所属的集合编号
		    v1=e[j].v1;
		    v2=e[j].v2;
		    s1=set[v1];
		    s2=set[v2];
		    //当两点的集合不同时,表示该边为生成树的一条边
		    if(s1!=s2){
		        printf("V%d-V%d=%d\n",v1,v2,e[j].w);
		        sum+=e[j].w;
		        k++; //最小生成树的边数增加
		        if(k>=n)    break;
		        for(i=1;i<=m;i++){
		            if(set[i]==s2)  set[i]=s1; //表示该点已经加入v1的集合中,同一集合
		        }
		    }
	    j++; //下一条边
	    }
	    printf("最小权值之和=%d\n",sum);
}

1.2

最小生成树的两种算法实现_第3张图片

2.1

void prim(int graph[][MAX],int n){
	    int mincost[MAX];
	    int mst[MAX];
	    int i,j,min,minid,sum=0;
	    for(i=2;i<=n;i++){
	        //以顶点1为起始点,并存放顶点1到邻接点的路径。
	        mincost[i]=graph[1][i];
	        mst[i]=1;
	    }
	    mst[1]=0;
	    for (i=2;i<=n;i++){
	        min=MAXCOST;
	        minid=0;
	        for(j=2;j<=n;j++){
	            //找出权重最短的路径和id
	            if(mincost[j]<min&&mincost[j]!=0){
	                min=mincost[j];
	                minid=j;
	            }
	        }
	        printf("V%d-V%d=%d\n",mst[minid],minid,min); 
	        //对权重求和
	        sum+=min;
	        //对最短路径的权重置0;
	        mincost[minid]=0;
	        for(j=2;j<=n;j++){
	            //路径更新
	            if(graph[minid][j]<mincost[j]){
	                mincost[j]=graph[minid][j];
	                mst[j]=minid;
	            }
	        }
	    }
	    printf("最小权重之和=%d\n",sum);
}

2.2

最小生成树的两种算法实现_第4张图片

4.分析

因为prim算法需要选点,通过遍历所有点集选出权重小的路径,并加入另一个点,再次遍历并查集,所以时间复杂度与顶点数v有关。Prim算法的复杂度为T=O(|V|^2),适用于边数较多的稠密图
因为kruskal算法需要选边,通过先对边的权重进行升序排序,,在升序遍历权重对应的点,加入没有被选择过的点,所以时间复杂度与边数e有关。Kruskal算法的复杂度为T = O(|E|log|E|) 用于边数较少的稀疏图

5.源码

https://github.com/Chenzh0205/Algorithm/tree/main/%E4%BD%9C%E4%B8%9A1

6.参考资料

1.数据结构课程ppt
2.最小生成树构造算法–Prim算法,Kruskal算法(C语言)

你可能感兴趣的:(算法分析)