贪心法求最小生成树Kruskal

实验 贪心法求最小生成树Kruskal

Kruskal代码 in GitHub
实验目的

Kruskal算法同样也是解决最小生成树问题的一个算法,和Prim算法不同,Kruskal算法采用了边贪心的策略。
基本思想是在初始的状态隐去图中的所有边,这样图中每个顶点都自成一个连通块。对所有边按边权从小到大进行排序;按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在同一个连通块中,则把这条测试边加入到当前最小生成树中;否则,将边舍弃;反复执行上一个步骤,直到最小生成树中的边数等于总顶点数目减去1或者是测试完所有边时结束。当结束时,如果最小生成树的边数小于总顶点数目减去1,说明该图不连通。

问题描述
Kruskal算法

  1. 当前图中权值最小的边为V0V4。由于V0和V4在不同的连通块中,因此把边V0V4加入最小生成树中,此时最小生成树中有1条边,权值之和为1。
  2. 当前图的剩余边中边权最小的边为V1V2,权值为1。由于V1和V2在不同的连通块中,因此把边V1V2加入到最小生成树中,此时最小生成树中有2条边,权值之和为2。
  3. 当前图的剩余边中边权最小的边为V0V5,权值为2。由于V0和V5在不同的连通块中,因此把边V0V5加入到最小生成树中,此时最小生成树中有3条边,权值之和为4.
  4. 当前图的剩余边中边权最小的边为V4V5,权值为3.由于V4和V5在同一个连通块中,因此如果加入边V4V5,就会形成一个环,故不予处理。
  5. 当前图的剩余边中边权最小的边为V1V5,权值为3。由于V1和V5在不同的连通块中,因此把边V1V5加入最小生成树中,此时最小生成树中有4条边,权值之和为7。
  6. 当前图的剩余边中边权最小的边为V0V1,权值为4。由于V0和V1都在当前最小生成树中,因此如果加入V0V1,就会形成一个环,故不予处理。
  7. 当前图的剩余边中边权最小的边为V3V5,权值为4。由于V3和V5在不同的连通块中,因此把边V3V5加入到最小生成树中,此时最小生成树中的边数为5,恰好为总顶点数6减去1,因此Kruskal算法结束,所得到的最小生成树边权之和为11。

实验环境
程序设计语言:C++
编程工具:Dev-C++ 5.11

程序代码

#include 
#include
#include

using namespace std;

const int MAXV = 110;
const int MAXE = 10010;
//边集定义部分
struct edge
{
	int u, v;    //边的两个端点编号
	int cost;   //边权
}E[MAXE];       //最多有MAXE边
bool cmp(edge a, edge b)
{
	return a.cost < b.cost;
}
//并查集部分
int father[MAXV];//并查集数组
int findFather(int x)
{
	if (x == father[x])
		return x;
	else
	{
		int f = findFather(father[x]);
		father[x] = f;
		return f;
	}
}
//kruskal部分,返回最小生成树的边权之和,
//参数n为顶点个数,m为图的边数
int kruskal(int n, int m)
{
	//ans为所求边权之和,Num_Edge为当前生成树的边数
	int ans = 0, Num_Edge = 0;
	for (int i = 0; i < n; i++)             //顶点范围是[0,n-1]
	{
		father[i] = i;                     
	}
	sort(E, E + m, cmp);              //所有边按边权从小到大排序
	for (int i = 0; i < m; i++)           //枚举所有边
	{
		int faU = findFather(E[i].u);       
//查询测试边两个端点所在集合的根结点
		int faV = findFather(E[i].v);
		if (faU != faV)                 //如果不在一个集合中
		{
			father[faU] = faV;  
//合并集合(即把测试边加入最小生成树中)
			ans += E[i].cost;           //边权之和增加测试边的边权
			Num_Edge++;                //当前生成树的边数加1
			if (Num_Edge == n - 1)
			     break;         //边数等于顶点数减1时结束算法
		}
	}
	if (Num_Edge != n - 1)return -1;    //无法连通时返回-1
	else return ans;                 //返回最小生成树的边权之和
}
int main(){
	int n,m;
	cin >> n >> m;                         //顶点数及其边数
	for(int i = 0;i < m;i++){
		cin >> E[i].u >> E[i].v >> E[i].cost;   //两个端点编号、边权
		 
	} 
	int ans = kruskal(n,m);             //Kruskal算法入口
	cout << ans ;
	return 0; 
}

运行结果
INPUT:
6 10 //输入描述问题中的
0 1 4 //6个顶点、10条边
0 4 1 //0号顶点与1号顶点的无向边的边权为4
0 5 2
1 2 1
1 5 3
2 3 6
2 5 5
3 4 5
3 5 4
4 5 3

OUTPUT:

11

分析体会(复杂性分析&程序的优劣&改进)
简单来说每次选择图中最小边权的边,若边两端的顶点在不同的连通块中,那么将这条边加入最小生成树中。对Kruskal算法,由于需要判断边的两个端点是否在不同的连通块中,因此边的两个端点的编号是一定需要的,在后面定义数组中查找下标,而在算法中,涉及到边权、权值,因此也必须要有边权。则可以定义一个结构体edge,再定义一个布尔型cmp函数,来判断权值大小。还要定义一个sort排序函数来让数组E按照边权从小到大排序。
引入并查集的概念,则是要考虑到算法的两个问题:一个是如何判断测试边的两个端点是否在不同的连通块中。另一个是如何将测试边加入到最小生成树中。
对于代码优化
这里可以使用路径压缩方式将并查集部分代码进行优化,以此减少时间复杂度。

你可能感兴趣的:(高级算法,最小生成树)