求無向圖最小割
先解釋下名詞的意思。
無向圖的割:就是去掉一些邊,使得原圖不連通,最小割就是要去掉邊的數量最小。
解決這個問題的常用辦法就是Stoer-Wagner 演算法;
先說下這個演算法的步驟後面給出證明:
1.min=MAXINT,固定一個頂點P
2.從點P用類似prim的s演算法擴展出“最大生成樹”,記錄最後擴充套件的頂點和最後擴充套件的邊
3.計算最後擴充套件到的頂點的切割值(即與此頂點相連的所有邊權和),若比min小更新min
4.合併最後擴充套件的那條邊的兩個端點為一個頂點
5.轉到2,合併N-1次後結束
6.min即為所求,輸出min
這個演算法過程不難理解,我在這裡還是簡單的演示下演算法的過程,
任選一個點P,開始了擴充套件之路初始化wage陣列的值都是0,並且已把點P放入了已擴充套件點的集合;
第一個點P擴充套件完的wage陣列的值如下圖
(圖中邊上的權值是表示兩點間相連的邊的條數,也可以認為是兩點間的連通度)
x
wage[4]的值最大這時候把點4加入到已訪問集合;
下一次就選取wage陣列最大值的點(也就是點4)開始向外擴充套件,注意在擴充套件點4的時候點P不會再被更新,
因為它已經進入被擴充套件集合了。過程同上。
反覆擴充套件。。。
擴充套件到最後兩個點的時候我們記為S,T;
其中T是最後一個點;
這時候我們得到了wage[S] 和 wage[T];
現在有如下性質:
1,wage[T]是將單點T分離出原圖的最小的割的值(這裡說的是把T這個點單獨拿出去);
2,wage[T]的值是將S或T單獨分離出原圖的最小割值中較小的那一個;
首先性質一很好證明,因為你最後更新到wage[T]的時候 wage[T]的值就是所有與它相連的邊的權值的和,顯然成立;
性質二的證明分為兩種情況,S與T有邊相連,和無邊相連;
S與T有邊相連時,在通過S進行擴充套件時,由於先選擇S所以擴充套件前wage[S]>=wage[T] 在擴充套件時 wage[T]的值必然會增加S與T之間的權值。
但是這個權值也是屬於S的
兩個相加了同一個數,由於之前wage[S]>=wage[T] 所以相加同一個數後不等式仍然成立;
同理S,T無邊相連也可以仿照上面的解釋;
下面要進行合點操作了,我們需要證明一件事情就是合點操作後不會影響最終的答案,
說明這個問題前,我們先進行下面的一個討論。
演算法進行到這就到了合併點的步驟了,我們先來分析下上面prim演算法的過程,
首先對於上面的性質1和性質2你要已經理解了下面的說明你才會覺得清晰。
到目前為止好像對於我們有用處的就是最後那兩個點S,T。我們不禁要問下,
如果只是為了選出兩個點,有必要這麼麻煩嗎(最大生成樹)?
直接隨意選兩個點,計算出對應的wage的值,
即計算把他們單獨分割出去的值,比較大小,小的記為T大的記為S,然後跟新min。
好像也滿足了上面所要的結果。顯然這麼做不對。我們考慮下面一種情況
我們先來想一想分割後的情況,假設我們找到了原圖的最小割然後按照最小割集進行分割,
那麼原圖一定被分為兩部分且只能是兩部分,記為A,B ;
A,B彼此連同,分割後的A,B 顯然是不連通的。
為了簡化證明過程我假設 原圖A與B 僅有一條邊相連。當然這條邊的值就是最小割的值。
這條邊連線的兩個點一個在A中一個在B中 分別記為啊a,b。
如果A中或B中只有一個點,那麼你按照隨意選點的方法不會影響最後的答案,我們這裡要討論A和B中點的個數大於1。
這時如果你還隨意的找兩個點進行更新min 合點操作就會出錯了。
這很好想,因為如果你選的是a,b 兩個點,
那麼你計算出的wage肯定是大於最小割,因為無論a,b都肯定與至少2個點相連,而最終的答案是a,b間邊的權值。
這時如果把a,b合點後再進行上面的重複操作顯然已經把最佳答案錯過了。
所以上面的這選點的prim是為了確定一個順序問題,我還以這個例子來說,
通過prim選出的兩個點S,T一定都在A中或者在B中(前提是A,B中包含2個或以上的點)
也只有在這個前提下進行點的合併才不會影響最後的答案。
我們先來證明下這兩個點一定在一邊而不會是一邊一個(前提是A,B中包含2個或以上的點)
證明:
不妨設我們prim 過程的第一個點在A中,那麼它一定先在A中的點開始擴充套件操作,
進行prim擴充套件若干次後首次計算B中點的wage 時
一定是A,B相連的那個點
此時wage[b]的值為最小割的值,接下來,
如果A中可擴充套件的點的wage值都大於wage[b],
那一定是A中的點都擴充套件玩了才會去擴充套件B中的點
那麼最後的兩個點一定都在B中。
否者,首次由b點開始擴充套件時候,
我們可以肯定的就是在A中可擴充套件的點的wage值都小於wage[b],
如果接下來B中的可擴充套件的點的wage值一直都大於A中可擴充套件點的wage值
那麼就是B中的點全都擴充套件完然後再進行A中的擴充套件最後兩個點就都在A中。
否則,在B中擴充套件若干次,再次回到A中擴充套件的時候,
B中可擴充套件點的wage值都小於A中的可擴充套件點的值,
由前面可知這個值一定小於wage[b]。
接下來就是按照這個思路,如果一直在A中那麼最後的點在B中,
如果再次回到B中擴充套件可知此時A中可擴充套件的點的wage值都小於B中可擴充套件點的wage值,反覆反覆下去,
如果最後A中一個點,B中一個點,會出現什麼情況呢?顯然最後的那個點的wage值小於wage[b]了。
這樣更新完原圖的最小割更小了,這顯然這顯然矛盾了。
矛盾的原因就在於prim的過程根本就沒法實現一個在A中一個在B中。
有了這麼個結論再討論合點不影響最後答案就很顯然了,稍微想一想畫一畫就懂了這裡就不仔細說了
如果S,T相連, 我已經更新了將S,T分割單獨分割出去時最小割的值,
那麼現在即使將S和T合併也不會影響全域性最小割的求解。
如果S,T不直接相連時,如果答案是S,T合併後的解顯然將兩者一起分離的時候已經將T單獨分開了。
肯定不會是最優的答案因為畫蛇添足啊。所以將S和T合併,並不會影響後面的計算。
到此我就粗略的說完了 無向圖最小割的演算法的正確性,以及證明,
合點操作之所以不影響答案的關鍵是prim演算法的步驟決定了選點的順序。
程式碼如下O(n^3)優化的其實效率並沒有提高。。
這個要根據題目資料決定。測試題目:廈大OJ 1100。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define maxn 105
#define inf 1000000
int node[maxn],cnt[maxn],vis[maxn],p[maxn][maxn];
int main()
{
int n,m,a,b,ans=inf;
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
if(i<n)
node[i]=i;
scanf("%d%d",&a,&b);
p[a][b]++;
p[b][a]++;
}
while(n>1)
{
memset(vis,0,sizeof(vis));
vis[node[0] ] =1;
int maxi = 1;
for(int i=0;i<n;i++)
{
cnt[node[i] ]=p[ node[i] ][node[0] ];
if(cnt[node[i] ]>cnt[ node[maxi] ])
{
maxi=i;
}
}
int pre = 0;
for(int s=1;s<n;s++)
{
if(s==n-1)
{
ans=min(ans,cnt[node[maxi] ]);
for(int k=0;k<n;k++)
p[node[k] ][node[pre] ] =p[node[pre] ][node [k] ] += p[node[k] ][node[maxi] ];
node[maxi] = node[--n];
}
vis[node[maxi] ] =1;
pre=maxi;
maxi=-1;
for(int i=0;i<n;i++)
{
if(!vis[node[i]])
{
cnt[node[i] ] += p[node[i] ][node[pre] ];
if(maxi==-1 || cnt[node[i] ]>cnt[node[maxi] ])
{
maxi = i;
}
}
}
}
}
printf("%d\n",ans);
return 0;
}