您的位置:首页 > 其它

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

2015-11-11 21:47 190 查看
习题链接: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也是最小生成树。由证明过程可以知道,最小生成树可以不是唯一的。


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

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

代码:

int pre[maxn];
int N, d;

int find(int x) {
return x == pre[x] ? x : pre[x] = find(pre[x]);
}

void unite(int x, int y) {
x = find(x);
y = find(y);
if(x != y) {
pre[y] = x;
}
}
inline void init() {
for(int i = 0; i < maxn; i++) {
pre[i] = i;
}
}


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

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

验题:poj2349

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <cstring>
#include <climits>
#include <complex>
#include <fstream>
#include <cassert>
#include <cstdio>
#include <bitset>
#include <vector>
#include <deque>
#include <queue>
#include <stack>
#include <ctime>
#include <set>
#include <map>
#include <cmath>

using namespace std;

const int maxn = 105;
const int inf = 0xffffff;
int d[maxn];
int G[maxn][maxn];
int vis[maxn];
int n, m;   //n:vertex m:edge

void init() {
memset(vis, 0, sizeof(vis));
for(int i = 0; i <= n; i++) {
d[i] = inf;
for(int j = 0; j <= n; j++) {
G[i][j] = G[j][i] = inf;
}
G[i][i] = 0;
}
}

int prim(int start) {
d[start] = 0;
for(int i = 1; i <= n; i++) {
int u = -1;
for(int j = 1; j <= n; j++) {
if(!vis[j]) {
if(u == -1 || d[j] < d[u]) {
u = j;
}
}
}
vis[u] = 1;
for(int j = 1; j <= n; j++) {
if(!vis[j]) {
d[j] = min(G[u][j], d[j]);
}
}
}
int sp = 0;
for(int i = 1; i <= n; i++) {
sp += d[i];
}
return sp;
}

int main() {
// freopen("in", "r", stdin);
int u, v, w;
while(~scanf("%d %d", &n, &m) && n) {
init();
while(m--) {
scanf("%d %d %d", &u, &v, &w);
if(w < G[u][v]) {
G[u][v] = G[v][u] = w;
}
}
printf("%d\n", prim(1));
}
}


View Code

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

转载请声明出处及作者,谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: