一、概述
最小生成树问题顾名思义,概括来说就是路修的最短。
接下来引入几个一看就明白的定义:
最小生成树相关概念:
带权图:边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权。
最小生成树(MST):权值最小的生成树。
最小生成树的性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。
完成构造网的最小生成树必须解决下面两个问题:
- 尽可能选取权值小的边,但不能构成回路;
- 选取n-1条恰当的边以连通n个顶点;
prim算法适合稠密图,kruskal算法适合简单图。
二、Prim算法
首先,假设有一棵只包含一个顶点v的树T,然后贪心地选取T和其它顶点之间相连的最小权值的边,并把它加到T中,不断进行这个操作,就可以得到一棵生成树了,可证这是一棵最小生成树
如何查找最小权值的边?假设已经求得的生成树的顶点的集合是X,把X集合和顶点v连接的边的最小权值记为mincost[v],在向X里添加顶点u时,只需要查看和u相连的边就可以了,对于每条边,更新mincost[v]=min(mincost[v],边(u,v)的权值)
如果每次都遍历为包含在X中的点的mincost[v],需要O(|V|2)的时间,不过和Dijkstra一样我们可以使用堆来维护mincost,时间复杂度就是O(|E|log|V|)
#include <cstdio> #include <queue> #include <vector> #include <utility> using namespace std; typedef pair<int, int> P; struct Edge { int to, cost; }; const int MAX_V=5000; const int INF=0x3f3f3f3f; int V, E, N; vector<Edge> G[MAX_V]; int res; int mincost[MAX_V]; bool used[MAX_V]; void prim() { for (int i=0; i<V; i++) { mincost[i]=INF; used[i]=false; } mincost[0]=0; priority_queue<P, vector<P>, greater<P> > que; que.push(P(0, 0)); while (!que.empty()) { P p=que.top(); que.pop(); int v=p.second; if (mincost[v]<p.first) continue; res+=mincost[v]; N++; used[v]=true; for (int i=0; i<G[v].size(); i++)//加入顶点v时只要查看和顶点v相连的边即可 { if (mincost[G[v][i].to]>G[v][i].cost && !used[G[v][i].to])//更新mincost,入队 { mincost[G[v][i].to]=G[v][i].cost; que.push(P(G[v][i].cost, G[v][i].to)); } } } } int main() { scanf("%d %d", &V, &E); for (int i=0; i<E; i++) { int s, t, c; scanf("%d %d %d", &s, &t, &c); Edge e; e.to=t-1; e.cost=c; G[s-1].push_back(e); e.to=s-1; G[t-1].push_back(e); } prim(); if (N <V) puts("orz"); else printf("%d ", res); }
三、kruskal算法
kruskal远离更为简单粗暴,但是需要借助并查集这一知识,来判断是否存在圈。
Kruskal算法按照边的权值的大小从小到大查看一遍,如果不产生圈(重边等也算在内),就把当前这条边加入到生成树中,一共加V-1条边。
如何判断是否产生圈? 假设现在要把连接顶点u和顶点v的边e加入生成树中。如果加入之前u和v不在同一个连通分量里,那么加入e也不会产生圈,反之,如果u和v在同一个连通分量里,那么一定会产生圈,可以用并查集高效地判断是否属于同一个连通分量
Kruskal在边的排序上最费时,算法的复杂度是O(|E|log|V|).
#include <cstdio> #include <queue> #include <vector> #include <algorithm> #include <cctype> #define num s-'0' using namespace std; struct edge { int u; int v; int cost; }; const int MAX_V=5000; const int MAX_E=200000; const int INF=0x3f3f3f3f; int V,E,n; edge es[MAX_E]; int res; int par[MAX_V]; int r[MAX_V]; void kruskal(); void init(); int find(int); void unite(int, int); bool same(int, int); void read(int &x){ char s; x=0; bool flag=0; while(!isdigit(s=getchar())) (s=='-')&&(flag=true); for(x=num;isdigit(s=getchar());x=x*10+num); (flag)&&(x=-x); } void write(int x) { if(x<0) { putchar('-'); x=-x; } if(x>9) write(x/10); putchar(x%10+'0'); } bool compare(const edge &e1, const edge &e2) { return e1.cost<e2.cost; } int main() { read(V);read(E); for (int i=0; i<E; i++) { read(es[i].u);read(es[i].v);read(es[i].cost); es[i].u--;es[i].v--; } kruskal(); write(res); putchar(' '); } void init() { for (int i=0; i<V; i++) { par[i]=i; r[i]=0; } } int find(int x) { if (par[x]==x) return x; return par[x]=find(par[x]); } void unite(int x, int y) { x=find(x); y=find(y); if (x==y) return; if (r[x]<r[y]) par[x]=y; else { par[y]=x; if (r[x]==r[y]) r[x]++; } } bool same(int x, int y) { return (find(x)==find(y)); } void kruskal() { sort(es, es+E, compare); init(); int n=0; for (int i=0; i<E; i++) { edge e=es[i]; if (!same(e.u, e.v)) { unite(e.u, e.v); res+=e.cost; ++n; } if (n==V-1) break; } }