菠菜

敏感而豁达

用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;
}

posted on 2012-06-23 20:52  ~菠菜~  阅读(1371)  评论(0)    收藏  举报

导航