無向連通網絡,去掉一個邊集可以使其變成兩個連通分量則這個邊集就是割集,最小割集當然就權和最小的割集。
使用最小切割最大流定理:
1.min=MAXINT,確定一個源點
2.枚舉匯點
3.計算最大流,並確定當前源匯的最小割集,若比min小更新min
4.轉到2直到枚舉完畢
5.min即為所求輸出min
復雜度很高:枚舉匯點要O(n),最短增廣路最大流算法求最大流是O((n^2)m)復雜度,在復雜網絡中O(m)=O(n^2),算法總復雜度就是O(n^5);哪怕采用最高標號預進流算法求最大流O((n^2)(m^0.5)),算法總復雜度也要O(n^4) 所以用網絡流算法求解最小割集復雜度不會低於O(n^4)。
------------------------------------------使用Stoer-Wagner算法
1.min=MAXINT,固定一個頂點P
2.從點P用“類似”prim的s算法擴展出“最大生成樹”,記錄最后擴展的頂點和最后擴展的邊
3.計算最后擴展到的頂點的切割值(即與此頂點相連的所有邊權和),若比min小更新min
4.合並最后擴展的那條邊的兩個端點為一個頂點(當然他們的邊也要合並,這個好理解吧?)
5.轉到2,合並N-1次后結束
6.min即為所求,輸出min
prim本身復雜度是O(n^2),合並n-1次,算法復雜度即為O(n^3),如果在prim中加堆優化,復雜度會降為O((n^2)logn)
幫助理解(reference http://www.cnblogs.com/ihopenot/p/5986772.html):考慮任意兩個點為s,t,如果全局最小割中s,t不在一個集合中,那么顯然全局最小割即為s-t最小割。否則我們將s,t縮成一個節點對於答案是沒有影響的。基於這一點,每次將問題規模減小后求解。一開始選擇的節點是作為s-t的中間節點集,因為每次擴展是選取聯系度最大的點擴展,所以中間節點集中點互相間的聯系度是大於st到中間點集的聯系度的,而最后加入的點t的聯系度是最小的,所以最小割即為這個點的聯系度,即為s通過中間節點集到t的流量加上s直接到t的流量。所以就證明了每次拓展求出的是s-t的最小割。
--------------------------------------------代碼
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAX_N 30
#define INF 0x3f3f3f3f
int G[MAX_N][MAX_N];
int v[MAX_N]; // v[i]代表節點i合並到的頂點
int w[MAX_N]; // 定義w(A,x) = ∑w(v[i],x),v[i]∈A
bool visited[MAX_N]; // 用來標記是否該點加入了A集合
int squ[MAX_N]; //記錄移除的節點次序
int index;
int stoer_wagner(int n)
{
int min_cut = INF,r=0;
for (int i = 0; i < n; ++i)
{
v[i] = i; // 初始還未合並,都代表節點本身
}
while (n > 1)
{
int pre = 0; // pre用來表示之前加入A集合的點(在t之前一個加進去的點)
memset(visited, 0, sizeof(visited));
memset(w, 0, sizeof(w));
for (int i = 1; i < n; ++i) //求出 某一輪最大生成樹的最后兩個節點,並且去除最后的t,將與t連接的邊歸並
{
int k = -1;
for (int j = 1; j < n; ++j) // 選取V-A中的w(A,x)最大的點x加入集合
{
if (!visited[v[j]])
{
w[v[j]] += G[v[pre]][v[j]];
if (k == -1 || w[v[k]] < w[v[j]])
{
k = j;
}
}
}
visited[v[k]] = true; // 標記該點x已經加入A集合
if (i == n - 1) // 若|A|=|V|(所有點都加入了A),結束
{
const int s = v[pre], t = v[k]; // 令倒數第二個加入A的點(v[pre])為s,最后一個加入A的點(v[k])為t
cout<<t<<"--->"<<s<<endl;
squ[r++]=t;
if(w[t]<min_cut)
{
min_cut=w[t];
index=r;
}
//min_cut = min(min_cut, w[t]); // 則s-t最小割為w(A,t),用其更新min_cut
for (int j = 0; j < n; ++j) // Contract(s, t)
{
G[s][v[j]] += G[v[j]][t];
G[v[j]][s] += G[v[j]][t];
}
v[k] = v[--n]; // 刪除最后一個點(即刪除t,也即將t合並到s)
}
// else 繼續
pre = k;
}
}
return min_cut;
}
int main(int argc, char *argv[])
{
int n, m;
while (scanf("%d%d", &n, &m) != EOF)
{
memset(G, 0, sizeof(G));
while (m--)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
G[u][v] += w;
G[v][u] += w;
}
int z=n;
//printf("%d\n", stoer_wagner(n));
cout<<"\r\n歸並的步驟為:"<<endl;
int res=stoer_wagner(n);
cout<<"\r\n最小割的總權值為: "<<res<<"\r\n圖划分為部分A:";
//cout<<"圖划分為部分A:";
for(int i=0;i<z;i++)
{
if(i==index)
cout<<"部分B:";
cout<<squ[i]<<" ";
}
}
return 0;
}
---------------------------------------示例


