用Kruskal和Prim算法求最小生成树

原理不多说,直接上代码。

代码一,Kruskal算法实现:

/*

参考自http://blog.csdn.net/niushuai666/article/details/6689285 有增删。



克鲁斯卡尔(Kruskal)算法(只与边相关)



算法描述:克鲁斯卡尔算法需要对图的边进行访问,所以克鲁斯卡尔算法的时间复杂度只和边有关系,可以证明其时间复杂度为O(eloge)。

算法过程:

1.将图各边按照权值进行排序

2.将图遍历一次,找出权值最小的边,(条件:此次找出的边不能和已加入最小生成树集合的边构成环),若符合条件,则加入最小生成树的集合中。不符合条件则继续遍历图,寻找下一个最小权值的边。

3.递归重复步骤1,直到找出n-1条边为止(设图有n个结点,则最小生成树的边数应为n-1条),算法结束。得到的就是此图的最小生成树。



克鲁斯卡尔(Kruskal)算法因为只与边相关,则适合求稀疏图的最小生成树。而prime算法因为只与顶点有关,所以适合求稠密图的最小生成树。



*/



#include <iostream>

#include <algorithm>



using namespace std;



#define SMALL_CASE // 只应用于26个字符的数据输入,输入节点按a~z编号。

//#define BIG_CASE // 超过应用于26个字符的数据输入,输入节点按1 2 3编号。



#ifdef SMALL_CASE

	#define MAX 26

	#define NODETYPE char

#elif BIG_CASE

	#define MAX 1000

	#define NODETYPE int

#endif



int father[MAX], son[MAX]; // father的值表示当前点所属连通分支的首结点索引 son表示以当前点为首结点的子结点数

int nodeCount, edgeCount;



struct Kruskal //存储边的信息

{

	int a;

	int b;

	int value;

};



// 比较两条边的权值

bool cmp(const Kruskal &a, const Kruskal &b)

{

	return a.value < b.value;

}



// 假设选中某边后,检查该边的一结点x是否原先已经在该连通分支中,如果在,返回该连通分支中第一条边的首结点。否则返回默认首结点。

int unionsearch(int x) //查找根结点+路径压缩

{

	return x == father[x] ? x : unionsearch(father[x]);

}



// 测试某边是否可以加入到当前树中来,并不构成圈

bool join(int x, int y)

{

	int root1, root2;

	root1 = unionsearch(x);

	root2 = unionsearch(y);

	if(root1 == root2) //为圈

	{

		return false;

	}

	// 如果不构成圈,那么这条边将被选中,这里更新father、son的值

	else if(son[root1] >= son[root2])

	{

		father[root2] = root1;

		son[root1] += son[root2];

	}

	else

	{

		father[root1] = root2;

		son[root2] += son[root1];

	}

	return true;

}



int main()

{

	int eCount/*已选择的边数*/, sum/*权值总和*/, flag/*求得最小生成树标志*/;

	Kruskal edge[MAX]; // 边集



	// 基本输入

	cout << "输入点数和边数:";

	cin >> nodeCount >> edgeCount;

	eCount = 0, sum = 0, flag = 0;

	for(int i = 1; i <= nodeCount; ++i) //初始化

	{

		father[i] = i;

		son[i] = 1;

	}

#ifdef SMALL_CASE

	cout << "输入每条边的连接信息(端点a 端点b 权值):" << endl;

	NODETYPE va, vb;

#elif BIG_CASE

	cout << "输入每条边的连接信息(端点1 端点2 权值):" << endl;

#endif



	// 将边信息和权值换算成相应整数

	for(int i = 1; i <= edgeCount ; ++i)

	{

#ifdef SMALL_CASE

		cin >> va >> vb >> edge[i].value;

		edge[i].a = va - 'a', edge[i].b = vb - 'a';

#elif BIG_CASE

		cin >> edge[i].a >> edge[i].b >> edge[i].value;

#endif

	}



	// 不断选择边,求最小生成树

	sort(edge + 1, edge + 1 + edgeCount, cmp); //按权值由小到大排序

	for(int i = 1; i <= edgeCount; ++i)

	{

		if(join(edge[i].a, edge[i].b)) //看此边是否加选择

		{

			eCount++; //边数加1

			sum += edge[i].value; //记录权值之和

#ifdef SMALL_CASE

			cout << (char)(edge[i].a + 'a') << "->" << (char)(edge[i].b + 'a') << endl;

#elif BIG_CASE

			cout << edge[i].a << "->" << edge[i].b << endl;

#endif

		}

		if(eCount == nodeCount - 1) //最小生成树条件:边数=顶点数-1

		{

			flag = 1;

			break;

		}

	}



	if(flag) // 是否生成了最小生成树

	{

		cout << "权值和:" << sum << endl;

	}

	else

	{

		cout << "data error." << endl;

	} 



	return 0;

}

代码二,Prim算法实现:

/*

参考自http://blog.csdn.net/niushuai666/article/details/6689285 有删减。



普利姆(Prime)算法(只与顶点相关)

 

算法描述:

普利姆算法求最小生成树时候,和边数无关,只和定点的数量相关,所以适合求稠密网的最小生成树,时间复杂度为O(n*n)。

算法过程:

1.将一个图的顶点分为两部分,一部分是最小生成树中的结点(A集合),另一部分是未处理的结点(B集合)。

2.首先选择一个结点,将这个结点加入A中,然后,对集合A中的顶点遍历,找出A中顶点关联的边权值最小的那个(设为v),将此顶点从B中删除,加入集合A中。

3.递归重复步骤2,直到B集合中的结点为空,结束此过程。

4.A集合中的结点就是由Prime算法得到的最小生成树的结点,依照步骤2的结点连接这些顶点,得到的就是这个图的最小生成树。

*/



#include <limits.h>

#include <iostream>

#include <cstring>



using namespace std;



#define INF 1000 // 边取值的最大值



#define SMALL_CASE



#ifdef SMALL_CASE // 输入对应a~z编号

	#define MAXN 26

#elif BIG_CASE // 输入对应从1开始编号

	#define MAXN 1000

#endif



int map[MAXN][MAXN], lowcost[MAXN]; // map图的点权值图,lowcost到某点i的最小花销,也就是与点i相连的边中的最小边

bool visit[MAXN]; // 点是否选中

int nodenum, sum; // 结点数 权值和



void prim()

{

	int minWeight, k; // 与某点k相连的最小边



	sum = 0;

	fill_n(visit, MAXN, false);

	visit[0] = true;

	for(int i = 0; i < nodenum; ++i) //初始化lowcost[i]

	{

		lowcost[i] = map[0][i];

	}



	int otherNode; // 与新加入点以最小边相连的另一点

	for(int i = 0; i < nodenum; ++i) //找生成树集合点集相连最小权值的边

	{

		// 找出已选点与未选点之间相连的最小边

		minWeight = INF;

		for(int j = 0; j < nodenum; ++j)

		{

			if(!visit[j] && minWeight > lowcost[j])

			{

				minWeight = lowcost[k = j]; // 注意k会在这里变化

			}

		}

		if(minWeight == INF) // 在数据正常的情况下,这里minWeight的值为INF的情况是刚好生成最小生成树。

		{

			break;

		}



		visit[k] = true; //加入最小生成树集合

		// 找到与k点相连的另一点

		for(int j = 0; j < nodenum; j++)

		{

			if(map[j][k] == minWeight)

			{

				otherNode = j;

				break;

			}

		}

#ifdef SMALL_CASE

		cout << (char)(otherNode + 'a') << "-->" << (char)(k + 'a') << endl;

#elif BIG_CASE

		cout << otherNode << "-->" << k << endl;

#endif

		sum += minWeight; //记录权值之和

		// 更新lowcost数组:因为新加入点k,使得集合间的最小花销发生了变化

		for(int j = 0; j < nodenum; ++j)

		{

			if(!visit[j] && lowcost[j] > map[k][j]) // 从本循环中i点到j点的花销大于从k点到j点的花销

			{

				lowcost[j] = map[k][j];

			}

		}

	}

}



int main()

{

	int edgenum;



	cout << "输入结点数和边数:";

	cin >> nodenum >> edgenum;



	if(nodenum > 0 && edgenum > 0 && edgenum <= (nodenum * (nodenum - 1) / 2))

	{

		fill_n((int*)map, MAXN * MAXN, INF); // 注意二维数组这里不要用memset函数哟

		cout << "输入每条边的连接信息(端点a 端点b 权值):" << endl;

		int a, b, cost;

		for(int i = 0; i < edgenum; ++i) //输入边的信息

		{

#ifdef SMALL_CASE

			char ca, cb;

			cin >> ca >> cb >> cost;

			a = ca - 'a', b = cb - 'a';

#elif BIG_CASE

			cin >> a >> b >> cost;

#endif

			if(cost < map[a][b])

			{

				map[a][b] = map[b][a] = cost;

			}

		}



		prim();

		cout << sum << endl; //最小生成树权值之和

	}

	else

	{

		cout << "data error." << endl;

	}



	return 0;

}

你可能感兴趣的:(最小生成树)