MST最小生成树及克鲁斯卡尔(Kruskal)算法

最小生成树MST,英文名如何拼写已忘,应该是min spaning tree吧。假设一个无向连通图有n个节点,那么它的生成树就是包括这n个节点的无环连通图,无环即形成树。最小生成树是对边上权重的考虑,最小生成树即树的所有边上权重值之和最小,最小指权重最小,即在含有 n 个顶点的连通网中选择 n-1 条边,构成一棵极小连通子图,并使该连通子图中 n-1 条边上权值之和达到最小。专业一点的解释:在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此的权重,若存在 T 为 E 的子集(即)且为无循环图,使得 w(T) 最小,则此 T 为 G 的最小生成树

omegaleft(t
ight)=sum_{left(u,v
ight)in t}omegaleft(u,v
ight)
最小生成树算法:

一、 克鲁斯卡尔(Kruskal)算法

克鲁斯尔卡算法的时间复杂度为O(ElogE),E为无向图中边的数目。适用于求边稀疏的网格的最小生成树。


克鲁斯卡尔算法的基本思想为:为使生成树上总的权值之和达到最小,则应使每一条边上的权值尽可能地小,自然应从权值最小的边选起,直至选出 n-1 条互不构成回路的权值最小边为止。具体作法如下:首先构造一个只含 n 个顶点的森林,即初始无向连通图G=(V,E),首先要对图中的各个边按照权值大小进行排序,这也体现了贪心算法的思想,资源排序,从而对局部最优的资源进行选择。要生成的最小生成树为T=(U,TE),然后考察G中的边的集合E,依权值从小到大从连通网中选择不使森林中产生回路的边加入到森林中去,直至该森林变成一棵树为止,这棵树便是连通网的最小生成树。

若被考察的边的两个顶点属于T的两个不同的连通分量,则将此边作为最小生成树的边加入到T中,同时把两个连通分量连接为一个连通分量;若被考察边的两个顶点属于同一个连通分量,则舍去此边,以免造成回路,如此下去,当T中的连通分量个数为1时,此连通分量便为G的一棵最小生成树。

具体代码如下:

 

 #include "stdio.h"  
	#include "stdlib.h"  
	struct edge  
	{  
    		int m;  
    		int n;  
    		int d;  
	}a[5010];  //边的结构体数组
	int cmp(const void *a,const void *b) //按升序排列  
	{  
    		return ((struct edge *)a)->d > ((struct edge *)b)->d;  
	}  
	int main(void)  
	{  
    		int i,n,t,num,min,k,g,x[100];  
    		printf("请输入顶点的个数:");  
    		scanf("%d",&n);  
    		
	 t=n*(n-1)/2;  //所有可能存在的边的条数最大值
    		for(i=1;i<=n;i++)  
       		x[i]=i;  
    		printf("请输入每条边的起、末端点、权值:/n");  
    		
	 for(i=0;i<t;i++)  
        		scanf("%d %d %d",&a[i].m,&a[i].n,&a[i].d); //输入每条边的权值  
    		qsort(a,t,sizeof(a[0]),cmp);  
    		min=num=0;  
    		for(i=0;i<t && num<n-1;i++)  
    		{  
        		for(k=a[i].m;x[k]!=k;k=x[k])  //判断线段的起始点所在的集合  
            		x[k]=x[x[k]];  
        		for(g=a[i].n;x[g]!=g;g=x[g])  //判断线段的终点所在的集合  
           	 	x[g]=x[x[g]];  
        		if(k!=g)  //如果线段的两个端点所在的集合不一样  
        		{  
            			x[g]=k;  
            			min+=a[i].d;  
            			num++;  
            			printf("最小生成树中加入边:%d %d/n",a[i].m,a[i].n);  
        		}  
    		}  
    		printf("最小生成树的权值为:%d/n",min);  
   	 	
	 system("pause");  
    		return 0;  
	}  

另一份参考代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
 
const int maxint = 999999;
 
typedef struct Road{
    int c1, c2; // a到b
    int value; // 权值
}Road;
 
int no;
int line;//记录实际关系数
Road road[100];//设一个比较大的值,实际看输入最小生成树中:边数e=n-1
int node[101];//最小生成树:n顶点数
 
bool myCmp(const Road &a, const Road &b)
{
    if(a.value < b.value)
        return 1;
    return 0;
}
 
//node[2]=1 node[8]=1 ,node[3]=1,共同的祖先,如果(3,8)加进去,则构成回路,不要
//有点像并查集
int Find_Set(int n)
{
    if(node[n] == -1)
        return n;
    return node[n] = Find_Set(node[n]);
}
 
bool Merge(int s1, int s2)
{
    int r1 = Find_Set(s1);
    int r2 = Find_Set(s2);
    if(r1 == r2)//如果相等证明构成回路,则直接返回一个0,不要把顶点加进来(下一步是加进去的)
        return 0;
    if(r1 < r2)
        node[r2] = r1;
    else
        node[r1] = r2;
    return 1;
}
 
int main()
{
    freopen("input.txt", "r", stdin);
    //初始化全为-1
    memset(node, -1, sizeof(node));
    scanf("%d",&no);
    scanf("%d",&line);
    int i;
    for(i=0; i<line; ++i)
    {
        cin >> road[i].c1 >> road[i].c2 >> road[i].value;
    }
    sort(road, road+line, myCmp);
    int sum = 0, count = 0; // sum是MST的值,count是记录已使用的点数
    for(i=0; i<line; ++i)
    {
        if(Merge(road[i].c1, road[i].c2))//如果返回的为0,则证明构成回路,不要加进
        {
            count ++;
            sum += road[i].value;
        }
        if(count == no-1)//e=n-1已经连通,可以退出
            break;
    }
    cout << sum << endl;
    return 0;
}
 


原文地址:https://www.cnblogs.com/suncoolcat/p/3292039.html