1、最小生成树的定义
一个连通图的生成树是该连通图的一个极小连通子图,它含有图中全部顶点,但只构成一棵树的(n-1)条边。对于一个带权连通无向图G的不同生成树,各棵树的边上的权值之和可能不同,边上的权值之和最小的树称为该图的最小生成树。
2、最小生成树的应用
比如要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
3、最小生成树的构造算法
图的邻接矩阵类型定义如下:
#define N 100 typedef char ElemType; //adjacency matrix graph typedef struct MGraph { ElemType vertexes[N]; int edges[N][N]; int n; }MGraph;
(1)普里姆(Prim)算法
Prim算法的核心思想是,从一个顶点开始,加入顶点集合,将顶点集合中所有顶点到其他顶点的边作为候选边,每次从候选边中挑选最小权值的边作为生成树的边,然后更新候选边,直到图的所有顶点都被加入为止。其步骤如下:
①初始化顶点集合为传入的那个顶点,初始化候选边为该顶点到其他顶点的所有边
②重复步骤③,直到图中所有顶点都被加入为止
③从候选边中挑选最小权值的边作为生成树的边,此时因为加入了新的顶点,所以需要更新候选边
代码实现:
void Prim(MGraph& g, int k) { int* w = new int[g.n]; //the least edge weight int* v = new int[g.n]; //vertexes of the least weight for (int i = 0; i < g.n; i++) { w[i] = g.edges[k][i]; v[i] = k; } w[k] = 0; for (int cnt = 1; cnt < g.n; cnt++) { //find the min val of w int imin = -1; for (int i = 0; i < g.n; i++) { if (w[i] != 0 && (imin == -1 || w[i] < w[imin])) imin = i; } //print cout << "(" << g.vertexes[v[imin]] << "," << g.vertexes[imin] << ")"; //update the least weight w[imin] = 0; for (int i = 0; i < g.n; i++) { if (g.edges[imin][i] != 0 && g.edges[imin][i] < w[i]) { w[i] = g.edges[imin][i]; v[i] = imin; } } } delete[] w, v; }
Prim算法包含两重for循环,其时间复杂度为O(n²)。
(2)克鲁斯卡尔(Kruscal)算法
Kruscal算法的基本思想是,将图的所有边按权值递增的顺序进行排序,然后每次依次从小到大选择一条边加入到生成树中,但加入的前提是加入这条边后不会构成回路。其步骤如下:
①将图的所有边按权值递增的顺序进行排序
②重复步骤③,直到图中所有顶点都被加入为止
③按照从小到大的顺序选择一条边,如果这条边未使生成树形成回路,就把这条边加入到生成树中
代码实现如下:
void Kruscal(MGraph& g) { //define the Edge typedef struct Edge { int u, v; int w; Edge(int _u, int _v, int _w) :u(_u), v(_v), w(_w) {} }Edge; //init the edges vector<Edge> v; for (int i = 0; i < g.n; i++) { for (int j = 0; j < g.n; j++) { auto w = g.edges[i][j]; if (w != INT16_MAX && w != 0) v.push_back(Edge(i, j, w)); } } //sort by weight increment sort(v.begin(), v.end(), [](Edge u, Edge v)->int { return u.w < v.w; }); //add all edges if it does not constitute a loop int* set = new int[g.n]; for (int i = 0; i < g.n; i++) set[i] = i; vector<Edge>::iterator iter = v.begin(); for (int cnt = 0; cnt < g.n - 1; ) { if (set[iter->u] != set[iter->v]) { cout << "(" << g.vertexes[iter->u] << "," << g.vertexes[iter->v] << ")"; cnt++; for (int i = 0; i < g.n; i++) { if (set[i] == set[iter->v]) set[i] = set[iter->u]; } } iter++; } delete[] set; }
Kruscal算法同样包含两重for循环,其时间复杂度为O(n²),但可以对Kruscal算法做两方面的优化,一是将边集排序改为堆排序,二是用并查集来判断新加入边是否构成回路,优化后Kruscal算法的时间复杂度为O(elog₂e),e为图的所有边。
4、测试
求出给定无向带权图的最小生成树。图的定点为字符型,权值为不超过100的整形。
输入
第一行为图的顶点个数n 第二行为图的边的条数e 接着e行为依附于一条边的两个顶点和边上的权值
输出
最小生成树中的边。
样例输入
6 10 ABCDEF A B 6 A C 1 A D 5 B C 5 C D 5 B E 3 E C 6 C F 4 F D 2 E F 6
样例输出
(A,C)(C,F)(F,D)(C,B)(B,E) (A,C)(D,F)(B,E)(C,F)(B,C)
代码如下:
#include <iostream> #include <map> #include <vector> #include <algorithm> using namespace std; #define N 100 typedef char ElemType; //adjacency matrix graph typedef struct MGraph { ElemType vertexes[N]; int edges[N][N]; int n; }MGraph; void Prim(MGraph& g, int k) { int* w = new int[g.n]; //the least edge weight int* v = new int[g.n]; //vertexes of the least weight for (int i = 0; i < g.n; i++) { w[i] = g.edges[k][i]; v[i] = k; } w[k] = 0; for (int cnt = 1; cnt < g.n; cnt++) { //find the min val of w int imin = -1; for (int i = 0; i < g.n; i++) { if (w[i] != 0 && (imin == -1 || w[i] < w[imin])) imin = i; } //print cout << "(" << g.vertexes[v[imin]] << "," << g.vertexes[imin] << ")"; //update the least weight w[imin] = 0; for (int i = 0; i < g.n; i++) { if (g.edges[imin][i] != 0 && g.edges[imin][i] < w[i]) { w[i] = g.edges[imin][i]; v[i] = imin; } } } delete[] w, v; } void Kruscal(MGraph& g) { //define the Edge typedef struct Edge { int u, v; int w; Edge(int _u, int _v, int _w) :u(_u), v(_v), w(_w) {} }Edge; //init the edges vector<Edge> v; for (int i = 0; i < g.n; i++) { for (int j = 0; j < g.n; j++) { auto w = g.edges[i][j]; if (w != INT16_MAX && w != 0) v.push_back(Edge(i, j, w)); } } //sort by weight increment sort(v.begin(), v.end(), [](Edge u, Edge v)->int { return u.w < v.w; }); //add all edges if it does not constitute a loop int* set = new int[g.n]; for (int i = 0; i < g.n; i++) set[i] = i; vector<Edge>::iterator iter = v.begin(); for (int cnt = 0; cnt < g.n - 1; ) { if (set[iter->u] != set[iter->v]) { cout << "(" << g.vertexes[iter->u] << "," << g.vertexes[iter->v] << ")"; cnt++; for (int i = 0; i < g.n; i++) { if (set[i] == set[iter->v]) set[i] = set[iter->u]; } } iter++; } delete[] set; } int main() { MGraph g; while(cin >> g.n) { //input int e; cin >> e; map<char, int> m; for (int i = 0; i < g.n; i++) { cin >> g.vertexes[i]; m[g.vertexes[i]] = i; } for (int i = 0; i < g.n; i++) for (int j = 0; j < g.n; j++) g.edges[i][j] = INT16_MAX; char u, v; int w; for (int cnt = 0; cnt < e; cnt++) { cin >> u >> v >> w; g.edges[m[u]][m[v]] = g.edges[m[v]][m[u]] = w; } //excute Prim(g, 0); cout << endl; //Prim algorithm Kruscal(g); cout << endl; //Kruscal algorithm } return 0; }
参考文献
[1] 李春葆.数据结构教程.清华大学出版社,2013.
[2] 最小生成树.百度百科[引用日期2018-04-29].