参考来源:http://blog.sina.com.cn/s/blog_700906660100v7vb.html
这里讨论无向联通图全局最小割
方法一 因为两点的最小割其实就是最大流,所以我们可以用dinic算法,枚举源点与汇点, 枚举时间复杂度O(n^2) , Dinic算法 时间复杂度O(n^2m),总的时间复杂度O(N^4M),显然当n很大的时候,需要的时间特别大,
方法二 Toer-Wagner算法,这个算法的正确性此处不证明,.核心的思想就是借鉴Prim算法,扩展最大生成树,记录最后加入的两个点
这里先定义两个操作,:
1.对于图G(V,E) , 对于V的一个子集A, i 属于 V-A , W[i] = 子集A中所有的点到 i 的直接距离之和(这里的直接距离,是指两点直接相连的距离,不借助其它点)
2. Unite(s,t) 合并两个点,将 t 点合并到 s 点中, 所有到 s 的 点的距离 加上到t的, s到其它点的距离 加上t到其他点的距离 (这些只处理指直接相连的点的距离)
用一张图说明合并操作
此时合并5,6点
算法流程如下
1.A代表点的集合,初始为空 , W[i] = 0;
2. n-1 次循环
令集合A={a},a为V中任意点
遍历所有的点,求出V-A中的w[i] ,
将最大的点 i加入集合A中
直到所有点都加入集合A后, 记录最后加入的为 t , 倒数第二个加入的为 s ,合并 s , t
3 . 最小割为每次循环的w[t] 中的最小值
下面详细说明求s,t的过程 ,以Poj (pku) 2914 Minimum Cut的第三个case为例,图为
G(V,E)
我们设法维护这样的一个w[],初始化为0;
我们把V-A中的点中w[i]最大的点找出来加入A集合;
V-A直到为空
w[]的情况如下
w[i] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
初始值 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
A=A∪{0} | --- | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
A=A∪{1} | | --- | 2 | 2 | 1 | 0 | 0 | 0 |
A=A∪{2} | | | --- | 3 | 1 | 0 | 0 | 0 |
A=A∪{3} | | | | --- | 1 | 0 | 0 | 1 |
A=A∪{4} | | | | | --- | 1 | 1 | 2 |
A=A∪{7} | | | | | | 2 | 2 | --- |
A=A∪{5} | | | | | | --- | 3 | |
A=A∪{6} | | | | | | | --- | |
上图一行一行的计算, 第三行, A中加入0点后, V-A 到A的w[i] , 如果有大小相同的,加入第一次出现的,即加入1点,
第四行, 加入1点后, 到A 的w[i] ,再从中取一个最大的,把点2加入A,
........
记录最后加入A的节点为t=6,倒数第二个加入A的为s=5,则s-t的最小割就为w[s],在图中体现出来的意思就是5-6的最小割为w[s]=3
然后我们合并 s ,t ,得到下图
G(V’,E’)
重复上述操作
w[i] | 0 | 1 | 2 | 3 | 4 | 5 | 7 |
初始值 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
A=A∪{0} | --- | 1 | 1 | 1 | 1 | 0 | 0 |
A=A∪{1} | | --- | 2 | 2 | 1 | 0 | 0 |
A=A∪{2} | | | --- | 3 | 1 | 0 | 0 |
A=A∪{3} | | | | --- | 1 | 0 | 1 |
A=A∪{4} | | | | | --- | 2 | 2 |
A=A∪{5} | | | | | | --- | 4 |
A=A∪{7} | | | | | | | --- |
s=5,t=7
合并5 ,7 得到
w[i] | 0 | 1 | 2 | 3 | 4 | 5 |
初始值 | 0 | 0 | 0 | 0 | 0 | 0 |
A=A∪{0} | --- | 1 | 1 | 1 | 1 | 0 |
A=A∪{1} | | --- | 2 | 2 | 1 | 0 |
A=A∪{2} | | | --- | 3 | 1 | 0 |
A=A∪{3} | | | | --- | 1 | 1 |
A=A∪{4} | | | | | --- | 4 |
A=A∪{5} | | | | | | --- |
s=4,t=5
合并4 ,5 得到
w[i] | 0 | 1 | 2 | 3 | 4 |
初始值 | 0 | 0 | 0 | 0 | 0 |
A=A∪{0} | --- | 1 | 1 | 1 | 1 |
A=A∪{1} | | --- | 2 | 2 | 1 |
A=A∪{2} | | | --- | 3 | 1 |
A=A∪{3} | | | | --- | 2 |
A=A∪{4} | | | | | --- |
s=3,t=4
一直循环N-1次,全局最小割即每次的W[t]中最小的
代码如下:
/*
* 全局最小割Stoer-Wagner算法
* 时间复杂度O(n^3) , 求s,t的时候可以优化到nlogn , 总体 O(n^2logn),此处不优化
* 从0开始标号
* 无向联通图的最小割
*/
const int SIZE = 520;
int graph[SIZE][SIZE];
bool ISCombine[SIZE];
bool ISvis[SIZE];
int W[SIZE];
int N,M;
//合并两个点,将 t 并入 s
inline void _unite( int s ,int t){
ISCombine[t] = true;
for (int i = 0;i < N;++i){
graph[s][i] += graph[t][i];
graph[i][s] += graph[i][t];
}
}
//寻找最大的W[i] , 并记录最后两个s,t下标
int _SearchMaxWi( int &s, int &t ){
memset( ISvis , 0 , sizeof(ISvis) );
memset( W , 0 , sizeof(W) );
int tmpj = 1000;
for (int i = 0;i < N;++i ) {
int MAX = INT_MIN;
for (int j = 0;j < N ; ++j)
if ( !ISCombine[j] && !ISvis[j] && W[j] > MAX )
MAX = W[j] , tmpj = j;
if(t == tmpj) return W[t];
ISvis[tmpj] = true;
s = t , t = tmpj;
for (int j = 0;j < N;j++)
if ( !ISvis[j] && !ISCombine[j] )
W[j] += graph[t][j];
}
return W[t];
}
int Stoer_Wagner(){
memset(ISCombine , 0, sizeof(ISCombine));
int ans = INT_MAX;
int s,t;
for (int i = 0;i < N-1;++i ){
s = t = -1;
int tmp = _SearchMaxWi(s,t);
//cout <<s<<" "<<t<<endl;
ans = min(ans, tmp); //用每次的W[i]更新ans
_unite(s,t); //合并s,t
}
return ans;
}