首先要知道什麼是割(cut)。割是把圖的節點劃分成兩個集合s和t,那麼有一些邊的端點是分別處於s和t中的。所謂最小割就是使這種邊的數目最少的劃分。
karger演算法是隨機演算法,它的描述很簡單:
每次隨機選擇一條邊,把邊的兩個端點合二為一。原來與這兩個點鄰接的點,現在把邊連到合併後的節點去,把原來的點和邊刪除。
合併後可能會有平行邊,在鄰接矩陣裡記錄邊的數目。把形成的自環刪除。可以看到,合併前的兩個點與「外界」的連線邊數、合併後的點與「外界」的連線邊數(即合併點的度數),這兩者是一樣的。所以當合併至只剩兩個點時,相當於將原圖的點劃分成兩個集合。這兩個點之間的連邊數就是形成的割的邊數。
現在分析下演算法正確的概率。因為乙個圖可能有多個最小割,我們來計算演算法得到某個特定最小割的概率。設最小割交叉邊的數目為c。那麼圖中每個點的度數至少為c(因為如果某個點的度數小於c,可以把這個點和其他點分開,形成的最小割邊數小於c,與假設矛盾。注意這個結論在合併點過程中仍然成立,因為前面說過了,合併得到的點的度數等於它代表的集合與外界的連線數)。如果圖有n個節點,那麼至少有n*c/2條邊。我們不能選特定的c條邊,否則就不是特定的割了。不選這c條邊的概率是(1-c/邊數)>= (1-2/n). 每合併一次,點的數目減一。那麼在整個過程中都不選那c條邊的概率是》=(1-2/n)*(1-2/(n-1))*...*(1-2/3) = 2/(n*(n-1)) >= 1/n2.
這個正確概率看著好像不很高。不過我們執行演算法多次。在每次執行中,沒得到特定最小割的概率<=1-1/n2。那麼執行m次沒得到的概率<=(1-1/n2)m。由於1-1/n2
<=e-1/n^2(因為1+x<=ex),當m=n2,失敗的概率小於1/e。當m=n2ln(n)時,失敗的概率更是不到1/n,考慮到一般圖的節點數量,可以忽略不計。
插播:n個節點的圖不同最小割的數量最多多少?
答案:n*(n-1)/2. 前面我們看到karger演算法找到特定最小割的概率》=2/(n*(n-1)). 設有r個最小割。因為得到這些割是互斥事件,所以得到任意乙個最小割的概率》=2/(n*(n-1))*r. 因為概率最大為1,r最大為n*(n-1)/2。這是可以取到的。比如n個點的環,任意刪除兩條邊都得到乙個獨特的最小割(最小割邊數為2),因此為n*(n-1)/2.
1.雖然這個演算法的描述簡潔易懂,但要實現好還是讓我頗費思量。先來拋個磚,給個o(n2)的實現,n是結點數。這個實現按照演算法的字面描述,對於隨機選定的一條邊的兩個結點v1,v2(v1
#include usingview codenamespace
std;
class
graph
;int
graph::getnodenum()
void graph::getedgeends(int edgeindex, int& row, int&col)
for(col=1;col<=n;col++)
}int
graph::initforcontract()
}else
int edgenum = 0
;
for(int i=1; i<=n; i++)
edgenum +=deg[i];
}return
edgenum;
}int
graph::contract()
else
if(adjmatrix[v2][i])
}deg[v2] = 0
; }
return edgenum/2;}
int graph::mincut(int
niter)
return
mincut;
}void parse(char str, vectorint> > &adjlist)
adjlist.push_back(lst);
}void buildgraph(char *fname, graph &g)
intmain()
2.另外的方法是事先打亂邊的順序,然後遍歷邊,把邊的兩端點連起來(而不是刪邊了),直到只剩兩個連通分量為止,剩下的邊裡端點在不同連通分量的邊的數目就是割的大小。
查詢邊的端點是否處於不同的連通分量,(是的話)把兩個連通分量連起來可以用並查集(此時總連通分量數減一)。
#include usingview codenamespace
std;
class
unionfind
;void unionfind::makeset(int
n)int unionfind::find(int
x)void unionfind::union(int x, int
y) componentnum--;
}}class graph:public
unionfind
;void
graph::makeedgelist()
}}int
graph::getnodenum()
intgraph::contract()
int cut = 0
;
for(; i)
return
cut;
}int graph::mincut(int
niter)
return
mincut;
}void parse(char str, vectorint> > &adjlist)
adjlist.push_back(lst);
}void buildgraph(char *fname, graph &g)
intmain()
3.我們來看看karger**裡的方法。我們需要確定的其實是使得只剩兩個連通分量的邊序列的最短字首。知道了這個,在剩下的邊裡求交叉邊的數量就容易了。注意到遍歷邊的過程中,連通分量的數目是單調減少的,因此我們可以二分。然而每次需要o(m)(m是邊數)來確定連通分量數目(用bfs之類),總複雜度o(m*log(m)),不夠好。
另一種方法是先選前m/2條邊,如果形成了乙個連通分量,那麼多了,考慮前m/4。如果形成多於兩個,再考慮後m/2的前一半。如果剛好是兩個,則可以直接計算割了。其實也是二分,關鍵是在已有的圖上加邊看連通分量的變化,還沒想好怎麼寫。
最小割(Stoer Wagner演算法)
簡介 割 在乙個圖g v,e 中v是點集,e是邊集。在e中去掉乙個邊集c使得g v,e c 不連通,c就是圖g v,e 的乙個割 最小割 在g v,e 的所有割中,邊權總和最小的割就是最小割。例題 minimum cut 描述 n個頂點。下面是m行,a,b,c,這意味著有c個邊連線頂點a和b。inc...
Dinic求最大流 最小割
o v 2 e 建圖時建一條流量為0的反向邊,正向邊每減去流量f,反向邊增加流量f.對於無向圖當做兩條邊。cap 每條邊最大流量 建圖後 呼叫dinic 用bfs 為每個節點進行層次編號,在每種層次編號下,用dinic 即dfs找到所有增廣路,加到最大流結果。theme 給定m條邊,n個點,1為源點...
tarjan演算法求割點割邊
在上一節我們已經知道tarjan演算法可以求聯通圖,在這裡我們也運用tarjan的思想求割點與割邊,首先我們先來說說割點,那麼什麼事割點呢,先來看一張圖 a 來自網路 在 a 圖中,我們將a點以及與a點相連的邊全部去除,會發現這個聯通圖被分成了倆個聯通圖,乙個是節點f,另外乙個是餘下的所有的節點組成...