一.问题引入
图中的6个顶点分别代表6个村庄,线段的权值代表村庄之间的距离。请问如何找到最短的路径来访问每一个村庄,且每个村庄只访问一次。
二.解决
- 提取图的边,并将边按权值大小从小到大排列,并放入edge数组。如下:
-
创建根数组(辅助数组),数组下标代表顶点节点本身,其元素代表顶点的根节点,如下:
提示:这里的“根”并不是树结构中真正的根,一棵树中只有一个根,而这里的“根”泛指长辈,可能是父节点,也可能是“爷”节点。初始化根数组为-1,代表初始状态下每个顶点都各自代表一个集合。
-
将edge数组中的边从小到大依次放回图中,如果后续加入的边与图中已放入的边形成了环,那么将此边丢弃,继续将下一条边放入,规则同前。
形成环,即说明加入的这条边的起点和终点已经属于一个集合,有共同的根。加入边的过程就是多个子集不断合并的过程,同一集合中的顶点不可相连。前面的辅助数组就是用来判断起点与终点是否属于一个集合。具体实现看代码注释。
-
放入(顶点数-1)条边后,最小生成树(Minimum Spanning Tree)构建完成,即可结束循环。
一棵树有n个节点,则有n-1条边
三.细节
上述算法主要有两点需要考虑:
树的储存:常见的图储存结构有:邻接矩阵,邻接边表,十字链表,邻接多重表,边集数组。显然,上述算法中频繁涉及到对边的操作,所以边集数组是最佳选择。
集合的表示:使用辅助数组,存放各顶点的根顶点,如果两个顶点的根相同,则属于同一集合,另则相反。初始化辅助数组元素为-1,代表每个顶点本身就是根节点。
四.实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| #include<iostream> #define MAX_VEX 10 #define MAX_EDGE 100 #define TYPE int using namespace std; struct Edge { TYPE start; TYPE end; TYPE weight; }; void ini_gragh(int& vex, int& edge, Edge* gragh); void sort_edge(Edge* edges,int edge); int find_root(int parent[], int n); int main() { int vex; int edge; Edge gragh[MAX_EDGE]; ini_gragh(vex, edge, gragh); sort_edge(gragh, edge); int roots[MAX_VEX]; Edge MST[MAX_EDGE]; int count = 0; for (int i = 0; i < vex; i++) roots[i] = -1; for (int i = 0; i < MAX_EDGE; i++) { int vex_m = find_root(roots, gragh[i].start); int vex_n = find_root(roots, gragh[i].end); if (vex_m != vex_n) { MST[count] = gragh[i]; count++; roots[vex_m] = vex_n; } if (count == vex-1) break; } for (int i = 0; i < vex - 1; i++) { printf("(%d,%d)%d\n", MST[i].start, MST[i].end, MST[i].weight); } return 0; } void ini_gragh(int& vex, int& edge, Edge* gragh) { cout << "输入连通网顶点数:"; cin >> vex; cout << "输入连通网边数: "; cin >> edge; cout << "依次输入各边的起点,终点和权重(空格隔开):" << endl; for (int i = 0; i < edge; i++) { cin >> gragh[i].start >> gragh[i].end >> gragh[i].weight; } } void sort_edge(Edge* edges_arr,int edge) { Edge temp; for (int i = 0; i < edge; i++) { for (int k = 0; k < edge - i - 1; k++) { if (edges_arr[k].weight > edges_arr[k + 1].weight) { temp = edges_arr[k]; edges_arr[k] = edges_arr[k + 1]; edges_arr[k + 1] = temp; } } } } int find_root(int roots[], int n) { while (roots[n] > -1) n = roots[n]; return n; }
|
运行结果:
以上便是关于克鲁斯卡尔算法的剖析,有不对之处烦请读者指出。