Book--追溯 最小生成树各类写法及优化
2015-01-04 14:19:46
学习是螺旋性的,今天来复习一下最小生成树的prim和kruskal,及prim堆优化。
采用 POJ 1258 作为测试平台。(题意:输入N,并输入N×N的点距离邻接矩阵,输入MST总长度)
First:Prim(适合稠密图,复杂度:朴素:O(V*V);堆优化:O(E*logV)(关于这个复杂度还没怎么想明白orz...))
思路回顾:prim是从一个只包含一个点的点集出发,不断找出从当前点集到其余点的最小权边,并添加最小权边的终点进点集,直至点集包含所有点。(可以看做点集扩张)
简证:如果在选当前点集X的出边时不选择最小权边(假设为e),而选择另一条边(假设为f)并构成生成树T,也就是说连接X和V-X的是边f,那么如果我们把e加入T,必定构成圈,再把f删掉,此时T-f+e仍然为生成树,又因为cost(f) >= cost(e),所以T-f+e <= T,所以选择e更合算,得证。
(1)朴素prim:
细节:总共找n-1条边,到各个点的距离用dis[]数组维护,已入点集的用used[]数组标记
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <iostream> 11 #include <algorithm> 12 using namespace std; 13 #define lp (p << 1) 14 #define rp (p << 1|1) 15 #define getmid(l,r) (l + (r - l) / 2) 16 #define MP(a,b) make_pair(a,b) 17 typedef long long ll; 18 typedef unsigned long long ull; 19 typedef pair<int,int> pii; 20 const int INF = (1 << 30) - 1; 21 const int maxn = 110; 22 23 int N; 24 struct edge{ 25 int v,cost; 26 }e[maxn * maxn]; 27 int first[maxn],next[maxn * maxn],ecnt; 28 int dis[maxn],used[maxn]; 29 30 void Add_edge(int u,int v,int c){ 31 next[++ecnt] = first[u]; 32 e[ecnt].v = v; 33 e[ecnt].cost = c; 34 first[u] = ecnt; 35 } 36 37 int Prim(){ 38 int p = 1,mst = 0; 39 memset(used,0,sizeof(used)); 40 fill(dis + 1,dis + N + 1,INF); 41 used[1] = 1; 42 for(int i = first[1]; i != -1; i = next[i]){ 43 dis[e[i].v] = e[i].cost; 44 } 45 for(int k = 1; k < N; ++k){ 46 int tmin = INF; 47 for(int i = 1; i <= N; ++i) 48 if(!used[i] && dis[i] < tmin) tmin = dis[p = i]; 49 mst += tmin; 50 used[p] = 1; 51 for(int i = first[p]; i != -1; i = next[i]){ 52 int v = e[i].v; 53 if(!used[v] && dis[v] > e[i].cost) 54 dis[v] = e[i].cost; 55 } 56 } 57 return mst; 58 } 59 60 int main(){ 61 int tmp; 62 while(scanf("%d",&N) != EOF){ 63 memset(first,-1,sizeof(first)); 64 ecnt = 0; 65 for(int i = 1; i <= N; ++i){ 66 for(int j = 1; j <= N; ++j){ 67 scanf("%d",&tmp); 68 if(tmp) Add_edge(i,j,tmp); 69 } 70 } 71 printf("%d\n",Prim()); 72 } 73 return 0; 74 }
(2)堆(优先队列)优化prim:
细节:总共找n个点,用cnt计数器记录已经找的点数,已入点集的用used[]数组标记
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <iostream> 11 #include <algorithm> 12 using namespace std; 13 #define lp (p << 1) 14 #define rp (p << 1|1) 15 #define getmid(l,r) (l + (r - l) / 2) 16 #define MP(a,b) make_pair(a,b) 17 typedef long long ll; 18 typedef unsigned long long ull; 19 typedef pair<int,int> pii; 20 const int INF = (1 << 30) - 1; 21 const int maxn = 110; 22 23 int N; 24 struct edge{ 25 int v,cost; 26 friend bool operator < (edge a,edge b){ 27 return a.cost > b.cost; 28 } 29 }e[maxn * maxn]; 30 int first[maxn],next[maxn * maxn],ecnt; 31 int dis[maxn],used[maxn]; 32 33 void Add_edge(int u,int v,int c){ 34 next[++ecnt] = first[u]; 35 e[ecnt].v = v; 36 e[ecnt].cost = c; 37 first[u] = ecnt; 38 } 39 40 int Prim(){ 41 priority_queue<edge> PQ; 42 int cnt = 0,mst = 0; 43 memset(used,0,sizeof(used)); 44 fill(dis + 1,dis + N + 1,INF); 45 edge s; 46 s.v = 1,s.cost = 0; 47 PQ.push(s); 48 while(cnt < N){ 49 edge x = PQ.top(); PQ.pop(); 50 if(used[x.v]) continue; 51 used[x.v] = 1; 52 cnt++; 53 mst += x.cost; 54 for(int i = first[x.v]; i != -1; i = next[i]){ 55 int v = e[i].v; 56 if(!used[v] && dis[v] > e[i].cost){ 57 dis[v] = e[i].cost; 58 PQ.push(e[i]); 59 } 60 } 61 } 62 return mst; 63 } 64 65 int main(){ 66 int tmp; 67 while(scanf("%d",&N) != EOF){ 68 memset(first,-1,sizeof(first)); 69 ecnt = 0; 70 for(int i = 1; i <= N; ++i){ 71 for(int j = 1; j <= N; ++j){ 72 scanf("%d",&tmp); 73 if(tmp) Add_edge(i,j,tmp); 74 } 75 } 76 printf("%d\n",Prim()); 77 } 78 return 0; 79 }
(3)堆(手敲版)优化prim:
细节:手写堆成员函数:clear() , push(..) , pop() , top()
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <iostream> 11 #include <algorithm> 12 using namespace std; 13 #define lp (p << 1) 14 #define rp (p << 1|1) 15 #define getmid(l,r) (l + (r - l) / 2) 16 #define MP(a,b) make_pair(a,b) 17 typedef long long ll; 18 typedef unsigned long long ull; 19 typedef pair<int,int> pii; 20 const int INF = (1 << 30) - 1; 21 const int maxn = 110; 22 23 int N; 24 struct edge{ 25 int v,cost; 26 }e[maxn * maxn]; 27 int first[maxn],next[maxn * maxn],ecnt; 28 int dis[maxn],used[maxn]; 29 30 struct Heap{ 31 edge t[maxn * maxn]; 32 int sz; 33 void clear(){ sz = 0;} 34 void push(edge x){ //插入,放最后一个位置,逐渐上调 35 int p = ++sz; 36 while(p > 1){ 37 int fa = p / 2; 38 if(t[fa].cost <= x.cost) break; 39 t[p] = t[fa]; 40 p = fa; 41 } 42 t[p] = x; 43 } 44 void pop(){ //出堆,最后一个元素调顶,逐渐下调 45 edge x = t[sz--]; 46 int p = 1; 47 while(p * 2 <= sz){ 48 int a = p * 2,b = p * 2 + 1; 49 if(b <= sz && t[b].cost < t[a].cost) a = b; 50 if(t[a].cost >= x.cost) break; 51 t[p] = t[a]; 52 p = a; 53 } 54 t[p] = x; 55 } 56 edge top(){ 57 return t[1]; 58 } 59 }; 60 61 void Add_edge(int u,int v,int c){ 62 next[++ecnt] = first[u]; 63 e[ecnt].v = v; 64 e[ecnt].cost = c; 65 first[u] = ecnt; 66 } 67 68 int Prim(){ 69 Heap h; h.clear(); 70 int cnt = 1,mst = 0; 71 memset(used,0,sizeof(used)); 72 fill(dis + 1,dis + N + 1,INF); 73 used[1] = 1; 74 for(int i = first[1]; i != -1; i = next[i]){ 75 dis[e[i].v] = e[i].cost; 76 h.push(e[i]); 77 } 78 while(cnt < N){ 79 edge x = h.top(); h.pop(); 80 if(used[x.v]) continue; 81 used[x.v] = 1; 82 cnt++; 83 mst += x.cost; 84 for(int i = first[x.v]; i != -1; i = next[i]){ 85 int v = e[i].v; 86 if(!used[v] && dis[v] > e[i].cost){ 87 dis[v] = e[i].cost; 88 h.push(e[i]); 89 } 90 } 91 } 92 return mst; 93 } 94 95 int main(){ 96 int tmp; 97 while(scanf("%d",&N) != EOF){ 98 memset(first,-1,sizeof(first)); 99 ecnt = 0; 100 for(int i = 1; i <= N; ++i){ 101 for(int j = 1; j <= N; ++j){ 102 scanf("%d",&tmp); 103 if(tmp) Add_edge(i,j,tmp); 104 } 105 } 106 printf("%d\n",Prim()); 107 } 108 return 0; 109 }
Second:Kruskal(适合稀疏图,复杂度:排序:O(E*logE) + Kru:O(E*logV))
思路回顾:与prim类似,kruskal也是点集扩张的过程,先将所有边排序,从最短边开始依次取边,且保证边的两点不同时在点集中(防止出现圈),直到点集==V
简易证明:对于当前点集X,和所有两点不同时在点集内的所有边,最短的边必定在最小生成树T中,证明思路其实和prim证法类似,设这条边(u,v)的某个点(设为u)先被加入点集,从点集到达其余点的最短边必定为(u,v),因此(u,v)这条边必定被选入T中。
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <iostream> 11 #include <algorithm> 12 using namespace std; 13 #define lp (p << 1) 14 #define rp (p << 1|1) 15 #define getmid(l,r) (l + (r - l) / 2) 16 #define MP(a,b) make_pair(a,b) 17 typedef long long ll; 18 typedef unsigned long long ull; 19 typedef pair<int,int> pii; 20 const int INF = (1 << 30) - 1; 21 const int maxn = 110; 22 23 int N,ecnt,fa[maxn]; 24 struct edge{ 25 int u,v,cost; 26 bool operator < (const edge &b) const{ 27 return cost < b.cost; 28 } 29 }e[maxn * maxn]; 30 31 int Find(int x){ 32 return fa[x] == x ? x : fa[x] = Find(fa[x]); 33 } 34 35 int Kruskal(){ 36 int mst = 0; 37 sort(e + 1,e + ecnt + 1); 38 for(int i = 1; i <= ecnt; ++i){ 39 int u = e[i].u; 40 int v = e[i].v; 41 int x = Find(u),y = Find(v); 42 if(x != y){ 43 mst += e[i].cost; 44 fa[x] = y; 45 } 46 } 47 return mst; 48 } 49 50 int main(){ 51 int tmp; 52 while(scanf("%d",&N) != EOF){ 53 for(int i = 1; i <= N; ++i) fa[i] = i; 54 ecnt = 0; 55 for(int i = 1; i <= N; ++i){ 56 for(int j = 1; j <= N; ++j){ 57 scanf("%d",&tmp); 58 if(tmp){ 59 ++ecnt; 60 e[ecnt].u = i; 61 e[ecnt].v = j; 62 e[ecnt].cost = tmp; 63 } 64 } 65 } 66 printf("%d\n",Kruskal()); 67 } 68 return 0; 69 }