• 最小生成树&&次小生成树


    习题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=85800#overview

    密码xwd

      关于生成树的定义:设图 G=(V, E) 是个连通图,当从图任一顶点出发遍历图G 时,将边集 E(G) 分成两个集合 T(G) 和 B(G)。其中 T(G)是遍历图时所经过的边的集合,B(G) 是遍历图时未经过的边的集合。显然,G1(V, T) 是图 G 的极小连通子图,即子图G1 是连通图 G 的生成树。

      生成树的用处很多,下面介绍一下最小生成树。

      顾名思义,最小生成树即边权和最小的生成树。假如我们在游玩,那么从一个景点出发到其他所有的景点获取最短的路径是很excited的。这就涉及到了最小生成建树的问题。

      获得最小生成树的算法有很多,其中最常用的是kruskal和prim算法。

      kruskal算法俗称破圈法,是一个贪心算法的典型例子。kruskal算法的思想是将所有的边排序,紧接着在这些边中取最小权值的边,并判断它双向的点是否连通以及是否成环。这样扫描一遍所有的边即可获得最小生成树。

      对kruskal算法的证明:

    对于一个无向加权连通图,总是存在一棵或以上的有限课生成树,而这些生成树中肯定存在至少一棵最小生成树。下面证明Kruskal算法构造的生成树是这些最小生成树中的一棵。
      设T为Kruskal算法构造出的生成树,U是G的最小生成树。如果T==U那么证明结束。如果T != U,我们就需要证明T和U的构造代价相同。由于T != U,所以一定存在k > 0条边存在于T中,却不在U中。接下来,我们做k次变换,每次从T中取出一条不在U中的边放入U,然后删除U一条不在T中的边,最后使T和U的边集相同。每次变换中,把T中的一条边e加入U,同时删除U中的一条边f。e、f按如下规则选取:a). e是在T中却不在U中的边的最小的一条边;b). e加入U后,肯定构成唯一的一个环路,令f是这个环路中的一条边,但不在T中。f一定存在,因为T中没有环路。
      这样的一次变换后,U仍然是一棵生成树。
      我们假设e权值小于f,这样变换后U的代价一定小于变换前U的代价,而这和我们之前假设U是最小生成树矛盾,因此e权值不小于f。
      再假设e权值大于f。由于f权值小于e,由Kruskal算法知,f在e之前从E中取出,但被舍弃了。一定是由于和权值小于等于f的边构成了环路。但是T中权值小于等于f(小于e)的边一定存在于U中,而f在U中却没有和它们构成环路,又推出矛盾。所以e权值不大于f。于是e权值等于f。
      这样,每次变换后U的代价都不变,所以K次变换后,U和T的边集相同,且代价相同,这样就证明了T也是最小生成树。由证明过程可以知道,最小生成树可以不是唯一的。

      如何判断无向图是否成环?这里介绍一个很巧妙很实用的数据结构:并查集。

    顾名思义,并查集是对一个集合进行维护的数据结构,它包含了两种基本操作:并和查(- -)

    代码:

     1 int pre[maxn];
     2 int N, d;
     3 
     4 int find(int x) {
     5     return x == pre[x] ? x : pre[x] = find(pre[x]);
     6 }
     7 
     8 void unite(int x, int y) {
     9     x = find(x);
    10     y = find(y);
    11     if(x != y) {
    12         pre[y] = x;
    13     }
    14 }
    15 inline void init() {
    16     for(int i = 0; i < maxn; i++) {
    17         pre[i] = i;
    18     }
    19 }

    pre数组存放的是角标为序号的节点的父节点(初始化将所有节点的父节点设置为自己)。查询节点的父节点时可以递归地调用find函数,不断更新和查找父节点。直到自己是自己的父亲为止。并查集看起来很像一个森林。并的操作更简单了,只要看看两个元素的父节点是否相同,如果不相同那么任意合并一个到另一个树上即可。

      回到kruskal算法,kruskal正是使用了这个精巧的数据结构维护了所有点的连通性。代码如下:

    验题:poj2349

     1 #include <algorithm>
     2 #include <iostream>
     3 #include <iomanip>
     4 #include <cstring>
     5 #include <climits>
     6 #include <complex>
     7 #include <fstream>
     8 #include <cassert>
     9 #include <cstdio>
    10 #include <bitset>
    11 #include <vector>
    12 #include <deque>
    13 #include <queue>
    14 #include <stack>
    15 #include <ctime>
    16 #include <set>
    17 #include <map>
    18 #include <cmath>
    19 
    20 using namespace std;
    21 
    22 typedef struct Point {
    23     int x;
    24     int y;
    25     double r;
    26 }Point;
    27 
    28 bool cmp(Point x, Point y) {
    29     return x.r < y.r;
    30 }
    31 
    32 const int maxn = 222222;
    33 priority_queue<int> pq;
    34 int n, s, p;
    35 int x[maxn],y[maxn];
    36 double d[maxn];
    37 int pre[maxn];
    38 Point poi[maxn];
    39 
    40 void init() {
    41     while(!pq.empty()) pq.pop();
    42     for(int i = 0; i <= maxn; i++) {
    43         pre[i] = i;
    44     }
    45 }
    46 
    47 int find(int x) {
    48     return x == pre[x] ? x : pre[x] = find(pre[x]);
    49 }
    50 
    51 bool unite(int x, int y) {
    52     x = find(x);
    53     y = find(y);
    54     if(x != y) {
    55         pre[x] = y;
    56         return 1;
    57     }
    58     return 0;
    59 }
    60 
    61 int main() {
    62     // freopen("in", "r", stdin);
    63     scanf("%d", &n);
    64     while(n--) {
    65         init();
    66         scanf("%d %d", &s, &p);
    67         for(int i = 0; i < p; i++) {
    68             scanf("%d %d", &x[i], &y[i]);
    69         }
    70         int cnt = 0;
    71         for(int i = 0; i < p; i++) {
    72             for(int j = i+1; j < p; j++) {
    73                 poi[cnt].x = i;
    74                 poi[cnt].y = j;
    75                 poi[cnt++].r = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
    76             }
    77         }
    78         sort(poi, poi+cnt, cmp);
    79         int cur = 0;
    80         for(int i = 0; i < cnt; i++) {
    81             if(unite(poi[i].x, poi[i].y)) {
    82                 d[cur++] = poi[i].r;
    83             }
    84         }
    85         printf("%.2f
    ", d[cur-s]);
    86     }
    87 }
    kruskal

      还有prim算法,与dijkstra算法非常相似,只是将dijkstra算法每次进行的松弛操作的d[j] = min(d[u]+G[u][j], d[j]);变成d[j] = min(G[u][j], d[j]);。每次贪心地选取从集合S到当前点的最小距离,之后将所有“集合到当前点的最小距离”相加即可。

     1 #pragma warning(disable:4996)
     2 
     3 
     4 #include <algorithm>
     5 #include <iostream>
     6 #include <iomanip>
     7 #include <cstring>
     8 #include <climits>
     9 #include <complex>
    10 #include <fstream>
    11 #include <cassert>
    12 #include <cstdio>
    13 #include <bitset>
    14 #include <vector>
    15 #include <deque>
    16 #include <queue>
    17 #include <stack>
    18 #include <ctime>
    19 #include <set>
    20 #include <map>
    21 #include <cmath>
    22 
    23 using namespace std;
    24 
    25 const int maxn = 105;
    26 const int inf = 0xffffff;
    27 int d[maxn];
    28 int G[maxn][maxn];
    29 int vis[maxn];
    30 int n, m;    //n:vertex m:edge
    31 
    32 void init() {
    33     memset(vis, 0, sizeof(vis));
    34     for(int i = 0; i <= n; i++) {
    35         d[i] = inf;
    36         for(int j = 0; j <= n; j++) {
    37             G[i][j] = G[j][i] = inf;
    38         }
    39         G[i][i] = 0;
    40     }
    41 }
    42 
    43 int prim(int start) {
    44     d[start] = 0;
    45     for(int i = 1; i <= n; i++) {
    46         int u = -1;
    47         for(int j = 1; j <= n; j++) {
    48             if(!vis[j]) {
    49                 if(u == -1 || d[j] < d[u]) {
    50                     u = j;
    51                 }
    52             }
    53         }
    54         vis[u] = 1;
    55         for(int j = 1; j <= n; j++) {
    56             if(!vis[j]) {
    57                 d[j] = min(G[u][j], d[j]);
    58             }
    59         }
    60     }
    61     int sp = 0;
    62     for(int i = 1; i <= n; i++) {
    63         sp += d[i];
    64     }
    65     return sp;
    66 }
    67 
    68 int main() {
    69     // freopen("in", "r", stdin);
    70     int u, v, w;
    71     while(~scanf("%d %d", &m, &n) && m) {
    72         init();
    73         while(m--) {
    74             scanf("%d %d %d", &u, &v, &w);
    75             if(w < G[u][v]) {
    76                 G[u][v] = G[v][u] = w;
    77             }
    78         }
    79         int len = prim(1);
    80         if(len > inf) puts("?");
    81         else printf("%d
    ", len);
    82     }
    83 }
    prim

      同样地,也可以像dijkstra一样,使用堆优化。

     1 #include <algorithm>
     2 #include <iostream>
     3 #include <iomanip>
     4 #include <cstring>
     5 #include <climits>
     6 #include <complex>
     7 #include <fstream>
     8 #include <cassert>
     9 #include <cstdio>
    10 #include <bitset>
    11 #include <vector>
    12 #include <deque>
    13 #include <queue>
    14 #include <stack>
    15 #include <ctime>
    16 #include <set>
    17 #include <map>
    18 #include <cmath>
    19 
    20 using namespace std;
    21 
    22 typedef pair<int, int> PII; //w v
    23 
    24 typedef struct E{
    25     int w;
    26     int v;
    27     E() {}
    28     E(int vv, int ww) : v(vv), w(ww) {}
    29 }E;
    30 
    31 const int inf = 0x7fffff;
    32 const int maxn = 111111;
    33 int n, nn;
    34 int vis[maxn], d[maxn];
    35 vector<E> e[maxn];
    36 priority_queue<PII, vector<PII>, greater<PII> > pq;
    37 
    38 int prim(int s) {
    39     int mst = 0;
    40     memset(vis, 0, sizeof(vis));
    41     for(int i = 0; i <= n; i++) d[i] = inf;
    42     while(!pq.empty()) pq.pop();
    43     d[s] = 0;
    44     pq.push(PII(0, 1));
    45     while(!pq.empty()) {
    46         PII cur = pq.top(); pq.pop();
    47         int w = cur.first;
    48         int v = cur.second;
    49         if(vis[v] || d[v] < w) continue;
    50         vis[v] = 1;
    51         mst += w;
    52         for(int i = 0; i < e[v].size(); i++) {
    53             int u = e[v][i].v;
    54             int w = e[v][i].w;
    55             if(!vis[u] && w < d[u]) {
    56                 d[u] = w;
    57                 pq.push(PII(d[u], u));
    58             }
    59         }
    60     }
    61     return mst;
    62 }
    63 
    64 int main() {
    65     // freopen("in", "r", stdin);
    66     int u, v, w;
    67     while(~scanf("%d", &n) && n) {
    68         nn = n * (n - 1) / 2;
    69         for(int i = 0; i <= n; i++) e[i].clear();
    70         for(int i = 0; i < nn; i++) {
    71             scanf("%d %d %d", &u, &v, &w);
    72             e[u].push_back(E(v, w));
    73             e[v].push_back(E(u, w));
    74         }
    75         printf("%d
    ", prim(1));
    76     }
    77 }
    prim+heap

    验题:

     1 #include <algorithm>
     2 #include <iostream>
     3 #include <iomanip>
     4 #include <cstring>
     5 #include <climits>
     6 #include <complex>
     7 #include <fstream>
     8 #include <cassert>
     9 #include <cstdio>
    10 #include <bitset>
    11 #include <vector>
    12 #include <deque>
    13 #include <queue>
    14 #include <stack>
    15 #include <ctime>
    16 #include <set>
    17 #include <map>
    18 #include <cmath>
    19 
    20 using namespace std;
    21 
    22 const int maxn = 105;
    23 const int inf = 0xffffff;
    24 int d[maxn];
    25 int G[maxn][maxn];
    26 int vis[maxn];
    27 int n, m;   //n:vertex m:edge
    28 
    29 void init() {
    30     memset(vis, 0, sizeof(vis));
    31     for(int i = 0; i <= n; i++) {
    32         d[i] = inf;
    33         for(int j = 0; j <= n; j++) {
    34             G[i][j] = G[j][i] = inf;
    35         }
    36         G[i][i] = 0;
    37     }
    38 }
    39 
    40 int prim(int start) {
    41     d[start] = 0;
    42     for(int i = 1; i <= n; i++) {
    43         int u = -1;
    44         for(int j = 1; j <= n; j++) {
    45             if(!vis[j]) {
    46                 if(u == -1 || d[j] < d[u]) {
    47                     u = j;
    48                 }
    49             }
    50         }
    51         vis[u] = 1;
    52         for(int j = 1; j <= n; j++) {
    53             if(!vis[j]) {
    54                 d[j] = min(G[u][j], d[j]);
    55             }
    56         }
    57     }
    58     int sp = 0;
    59     for(int i = 1; i <= n; i++) {
    60         sp += d[i];
    61     }
    62     return sp;
    63 }
    64 
    65 int main() {
    66     // freopen("in", "r", stdin);
    67     int u, v, w;
    68     while(~scanf("%d %d", &n, &m) && n) {
    69         init();
    70         while(m--) {
    71             scanf("%d %d %d", &u, &v, &w);
    72             if(w < G[u][v]) {
    73                 G[u][v] = G[v][u] = w;
    74             }
    75         }
    76         printf("%d
    ", prim(1));
    77     }
    78 }
    View Code

    关于次小生成树,我们可以首先求出最小生成树,然后将最小生成树上的边依次取下。向上添加其他的边,这样就可以求得次小生成树了。可以看这一个题解:http://www.cnblogs.com/vincentX/p/4946099.html

    转载请声明出处及作者,谢谢。

  • 相关阅读:
    Installing Oracle Database 12c Release 2(12.2) RAC on RHEL7.3 in Silent Mode
    周四测试
    假期生活
    《人月神话》阅读笔记三
    《人月神话》阅读笔记二
    《人月神话》阅读笔记一
    软件进度7
    软件进度6
    软件进度5
    软件进度4
  • 原文地址:https://www.cnblogs.com/kirai/p/4954425.html
Copyright © 2020-2023  润新知