最小生成树问题的引入:
对于一个无向图G(V, E),需要用图中的n - 1条边连接图中的n个顶点并且不产生回路所产生的树就叫做生成树,其中权值总和最小的就是最小生成树。
如何求解最小生成树问题:
譬如给定一个无向图G(V, E),要如何求出这个图的一个最小生成树呢?
下面我们先给出这个问题的一个总的解决方法:
我们先假设集合A为满足A为图G的最小生成树的边的集合。
条件P:A为G的最小生成树的子集。
起初 : 我们设定A为空集,显然此时集合A满足条件P。
添加边:每次我们找到一条边(u, v),使得A U { (u, v) } 这个集合依然满足条件P,我们找到的这条边(u, v)被称为集合A的安全边。
以上就是最小生成树的求解过程,我们可以很容易看出这个求解操作中的关键部分就是如何找出符合条件的安全边。那么下面我们先给出求解最小生成树问题的伪代码。
GENERIC-MST(G, w) A <- NULL Set while(A does not form a spanning tree) find an edge(u, v) that is safe for A A = A U {(u, v)} return A
Kruskal:
Kruskal算法思想:任意时刻的中间结果是一个森林。初始n个点的集合,每次选择权重最小且不会产生圈的边加入集合A,合并两个森林。为了方便检测环和加边,需要借助并查集。
Kruskal加边操作:贪心思想,每次择最优,保证不成环即可。
Kruskal具体操作:
初始化:每个顶点独立成为一个森林。
加边:在所有边中选择一条权重最小的边用来连接两个森林,所选择的这条边需要保证合并森林不会生成环。
下面给出Kruskal算法的伪代码:
MST-KRUSKAL(G, w) A <- NULL Set for each vertex v Make_Set(v) sort the edges of G.E into nondecreasing order by weight w for each edge(u, v) taken in nondecreasing order by weight if(Find(u) != Find(v)) A = A U {(u, v)} Union(u, v) return A
Kruskal算法参考代码:
1 typedef pair<int, int> pii; 2 const int maxn = 1000 + 5, maxe = 2000 + 5; 3 int n, e;//全局变量,图中的点和边的数目 4 struct Edge { 5 int x, y, cost; 6 } edge[maxe];//全局变量,edge[i]表示第i条边的信息 7 int ans = 0;//表示为最小生成树的边权和 8 int num = 0;//表示所求出的边的数量 9 pii spanning[maxn];//存储最小生成树的每条边 10 int head[maxn], Rank[maxn]; 11 12 void Make_Set(int n) { 13 for(int i = 1; i <= n; i ++) { 14 Rank[i] = 0; 15 head[i] = i; 16 } 17 } 18 19 int Find(int u) { 20 if(u == head[u]) return u; 21 else return head[u] = Find(head[u]); 22 } 23 24 void Union(int u, int v) { 25 int hu = Find(u), hv = Find(v); 26 if(Rank[hu] > Rank[hv]) 27 head[hv] = hu; 28 else { 29 head[hu] = hv; 30 if(Rank[hu] == Rank[hv]) Rank[hv] += 1; 31 } 32 } 33 34 bool Is_same(int u, int v) { 35 return Find(u) == Find(v); 36 } 37 38 bool cmp(Edge a, Edge b) { 39 return a.cost < b.cost; 40 } 41 42 int Kruskal() { 43 sort(edge + 1, edge + e + 1, cmp); 44 int cnt = n; 45 Make_Set(n); 46 for(int i = 1; i <= e; i ++) { 47 if(!Is_same(edge[i].x, edge[i].y)) { 48 Union(edge[i].x, edge[i].y); 49 ans += edge[i].cost; 50 spanning[++num].first = edge[i].x; 51 spanning[num].second = edge[i].y; 52 if(cnt == 1) break;//若只剩下一个连通块即最小生成树已经生成,则退出 53 } 54 } 55 return ans; 56 }
Prim:
Prim算法思想:任意时刻的中间结果都是一颗最小生成树的子树。从指定的一个点开始,每次都花最少的代价,用一条边把一个不在树中的结点加进来。为了方便选择离树最近的点,需要借助堆。
Prim加边操作:贪心策略,每次选择一条边(u, v)保证u已经是最小生成树子树内的结点,v是最小生成树子树内的所有结点u出发的边的tail结点,每次选择保证边(u, v)的权重最小。
Prim算法具体操作:
初始化:将初始点标记。
加边:每次找一条最短的两端分别为标记和未标记的边加入最小生成树子树并把未标记的点标记上。即每次加入一条安全边,每次扩展一个点由未标记为变已标记,
直至扩展至n个点。
下面给出Prim算法的伪代码:
MST-PRIM(G, w, source) for each vertex u u:key <- Infinite //表示距离结点u最近的结点到u的距离 u.head <- NULL//表示距离结点u最近的结点的编号 source:key <- 0 Q <- G.Vertex // the source vertex while(Q != NULLSet) u <- EXTRACT-MIN(Q)//Find and Delete for each v belong to G.Adj[u] if(v belong to Q and w(u, v) < v.key) v.head <- u; v.key <- w(u, v);
Q <- v
Prim算法实现代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <queue> 4 #include <map> 5 #include <vector> 6 using namespace std; 7 8 typedef pair<int, int> pii; 9 const int maxn = 100 + 5, maxe = 200 + 5, INF = 0x3f3f3f3f; 10 struct Edge { 11 int to, cost; 12 friend bool operator < (const Edge &a, const Edge&b) { 13 return a.cost > b.cost; 14 } 15 }; 16 vector<Edge> edge[maxe]; 17 int n, e, ans, dist[maxn]; 18 bool vis[maxn]; 19 20 void addedge(int u, int v, int w) { 21 edge[u].push_back({v, w}); 22 } 23 24 void MST_Prim(int source) { 25 memset(vis, false, sizeof vis); 26 for(int i = 2; i <= n; i ++) dist[i] = INF; 27 dist[source] = ans = 0; 28 priority_queue <Edge> Q; 29 Q.push({source, dist[source]}); 30 while(!Q.empty()) { 31 Edge u = Q.top(); 32 Q.pop(); 33 if(vis[u.to]) continue; 34 vis[u.to] = true; 35 ans += dist[u.to]; 36 for(int i = 0; i < edge[u.to].size(); i ++) { 37 Edge e = edge[u.to][i]; 38 if(dist[e.to] > e.cost) { 39 dist[e.to] = e.cost; 40 Q.push({e.to, dist[e.to]}); 41 } 42 } 43 } 44 } 45 46 int main() { 47 int x, y, w; 48 scanf("%d", &n); 49 for(int i = 1; i <= n; i ++) { 50 scanf("%d %d %d", &x, &y, &w); 51 addedge(x, y, w); 52 addedge(y, x, w); 53 } 54 MST_Prim(1); 55 for(int i = 1; i <= n; i++) edge[i].clear();//被这个坑了一天,一定要记住全局变量要清空 56 return 0; 57 }