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 }
View Code

(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 }
View Code

(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 }
View Code

 

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 }
View Code

 

转载于:https://www.cnblogs.com/naturepengchen/articles/4201308.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值