您的位置:首页 > 其它

朱刘算法计算有向的最小生成树

2014-10-20 17:03 246 查看
图论是ACM竞赛中比较重要的组成部分,其模型广泛存在于现实生活之中。因其表述形象生动,思维方式抽象而不离具体,因此深受各类喜欢使劲YY的Acmer的喜爱。这篇文章引述图论中有关有向图最小生成树的部分,具体介绍朱刘算法的求解思路,并结合一系列coding技巧,实现最小树型图O(VE)的算法,并在最后提供了该算法的模版,以供参考。

  关于最小生成树的概念,想必已然家喻户晓。给定一个连通图,要求得到一个包含所有顶点的树(原图的子图),使之所构成的边权值之和最小。在无向图中,由于边的无向性质,所以求解变得十分容易,使用经典的kruskal与prim算法已经足够解决这类问题。而在有向图中,则定义要稍微加上一点约束,即以根为起点,沿给定有向边,可以访问到所有的点,并使所构成的边权值之和最小,于是问题开始变得不一样了,我们称之为最小树型图问题。

  该问题是由朱永津与刘振宏在上个世纪60年代解决的,值得一提的是,这2个人现在仍然健在,更另人敬佩的是,朱永津几乎可以说是一位盲人。解决最小树型图的算法后来被称作朱刘算法,也是当之无愧的。

  首先我们来阐述下算法的流程:算法一开始先判断从固定根开始是否可达所有原图中的点,若不可,则一定不存在最小树形图。这一步是一个很随便的搜索,写多搓都行,不加废话。第二步,遍历所有的边,从中找出除根结点外各点的最小入边,累加权值,构成新图。接着判断该图是否存在环。若不存在,则该图便是所求最小树型图,当前权为最小权。否则对环缩点,然后回到第二步继续判断。

  这里存在一系列细节上的实现问题,以确保能够达到VE的复杂度。首先是查环,对于新图来说只有n-1条入边,对于各条入边,其指向的顶点是唯一的,于是我们可以在边表中添加from,表示该边的出发点,并考虑到如果存在环,则对环上所有边反向,环是显然同构的,于是最多作V次dfs就能在新图中找到所有的环,并能在递归返回时对顶点重标号进行缩点,此步的重标号可以用hash数组映射。然后就是重要的改边法,对于所有不在环上的边,修改其权为w-min(v),w为当前边权,min(v)为当前连接v点的最小边权。其数学意义在于选择当前边的同时便放弃了原来的最小入边。我们可以知道,每次迭代选边操作O(E),缩点操作O(V),更新权操作O(E),至少使一个顶点归入生成树,于是能在V-1步内完成计算,其复杂度为O(VE)。

以上为定根最小树型图,对于无固定根最小树型图,只要虚拟一个根连所有的点的权为边权总和+1,最后的结果减去(边权+1)即可。另外对于求所定的根标号,只要搜索被选中的虚边就可以判断了。



#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>

using namespace std;

const int N = 101, M = 10001, inf = 2147483647;
struct edge {
<span style="white-space:pre">	</span>int u, v, w;
} e[M];
int n, m, ans, id
, pre
, v
, inw
;

void zhu_liu(int root) {
<span style="white-space:pre">	</span>int s, t, idx = n;
<span style="white-space:pre">	</span>while (idx) {
<span style="white-space:pre">		</span>for (int i = 1; i <= n; ++i)
<span style="white-space:pre">			</span>inw[i] = inf, id[i] = -1, v[i] = -1;

<span style="white-space:pre">		</span>for (i = 1; i <= m; ++i) {
<span style="white-space:pre">			</span>s = e[i].u;
<span style="white-space:pre">			</span>t = e[i].v;
<span style="white-space:pre">			</span>if (e[i].w > inw[t] || s == t)
<span style="white-space:pre">				</span>continue;
<span style="white-space:pre">			</span>pre[t] = s;
<span style="white-space:pre">			</span>inw[t] = e[i].w;
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>inw[root] = 0;
<span style="white-space:pre">		</span>pre[root] = root;
<span style="white-space:pre">		</span>for (i = 1; i <= n; ++i) {
<span style="white-space:pre">			</span>if (inw[i] == inf) {
<span style="white-space:pre">				</span>printf("impossible\n");
<span style="white-space:pre">				</span>return;
<span style="white-space:pre">			</span>}
<span style="white-space:pre">			</span>ans += inw[i];
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>idx = 0;
<span style="white-space:pre">		</span>for (i = 1; i <= n; ++i)
<span style="white-space:pre">			</span>if (v[i] == -1) {
<span style="white-space:pre">				</span>t = i;
<span style="white-space:pre">				</span>while (v[t] == -1)
<span style="white-space:pre">					</span>v[t] = i, t = pre[t];
<span style="white-space:pre">				</span>if (v[t] != i || t == root)
<span style="white-space:pre">					</span>continue;
<span style="white-space:pre">				</span>id[t] = ++idx;
<span style="white-space:pre">				</span>for (s = pre[t]; s != t; s = pre[s])
<span style="white-space:pre">					</span>id[s] = idx;
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>if (idx == 0)
<span style="white-space:pre">			</span>continue;
<span style="white-space:pre">		</span>for (i = 1; i <= n; ++i)
<span style="white-space:pre">			</span>if (id[i] == -1)
<span style="white-space:pre">				</span>id[i] = ++idx;
<span style="white-space:pre">		</span>for (i = 1; i <= m; ++i) {
<span style="white-space:pre">			</span>e[i].w -= inw[e[i].v];
<span style="white-space:pre">			</span>e[i].u = id[e[i].u];
<span style="white-space:pre">			</span>e[i].v = id[e[i].v];
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>n = idx;
<span style="white-space:pre">		</span>root = id[root];
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>printf("%d\n", ans);
}
int main() {
<span style="white-space:pre">	</span>scanf("%d%d", &n, &m);
<span style="white-space:pre">	</span>for (int i = 1; i <= m; ++i)
<span style="white-space:pre">		</span>scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
<span style="white-space:pre">	</span>zhu_liu(1);
<span style="white-space:pre">	</span>return 0;
}


另附一个测试用例:

7 15

1 2 9

4 1 3

1 5 5

2 3 3

3 2 7

2 4 9

5 4 4

6 5 3

4 3 8

4 6 5

3 6 9

3 7 6

7 3 4

6 7 4

7 6 8

结果:29
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: