用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;
}
浙公网安备 33010602011771号