原理不多说,直接上代码。
代码一,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; }