zoukankan      html  css  js  c++  java
  • 最小生成树&&次小生成树

    习题链接: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

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

  • 相关阅读:
    yocto/bitbake 学习资源
    QEMU/KVM学习资源
    ubuntu 中创建和删除用户
    git 重命名本地和远程分支
    Ubuntu 上搭建 FTP 服务器
    gdb 常见用法
    git log 显示与特定文件相关的 commit 信息
    基于 qemu system mode 运行 arm 程序
    基于 qemu user mode 运行 aarch64 程序
    checking in(airport)
  • 原文地址:https://www.cnblogs.com/kirai/p/4954425.html
Copyright © 2011-2022 走看看