Today I am going to implement the Stoer-Wagner algorithm that finds minimum cuts in a weighted graph.
The algorithm is split into two functions
Function MinimumCut
Input: A graph G
Output: A pair of vertices a and b
Begin
Let V be the set of vertices of G
Let x be the either end of the edge with highest weight
Let A
While V \ A is non-empty
Find the edge e with the highest weight that is incident
to A and V \ A
Add the vertex of e that lies in V \ A to A
End While
Return the last two vertices in A
End
Function StoerWagner
Input: A graph G
A weight w
A subset X of vertices in G
Output: Optimal cut and its weight
Begin
If G contains only one edge then
Return w, X and the complement of X
Else
Let a and b be MinimumCut(G)
If the cut-weight of a is less than w then
Let w be cut-weight of a
Let X be a
End If
Merge a and b on G and adjust the weights of
the graph G accordingly
Call StoerWagner(G,w,X)
End
I am going to use a hashmap of edges with their weights. I am going to use sets of size 2 to represent edges. Here is an example:
def G {#{1 2} 2 #{1 5} 3
(2 5} 2 #{2 3} 3 #{2 6} 2
#{3 4} 4 #{3 7} 2
#{4 7} 2 #{4 8} 2
#{5 6} 3
#{6 7} 1
#{7 8} 3}) #{
#'stoer-wagner/G
I am going to need to utility functions. So here they are:
defn helper [a b]
(union (if (set? a) a #{a})
(if (set? b) b #{b})))
(
defn calculate-cut-weight [G v]
(->> (keys G)
(map #(if (contains? % v) (G %) 0))
(reduce +))) (
#'stoer-wagner/helper
#'stoer-wagner/calculate-cut-weight
Next is the function that implements the MimimumCut part of the algorithm:
defn find-minimum-cut [G]
(let [V (reduce union (keys G))]
(loop [A [(rand-nth (into [] V))]]
(let [C (difference V A)]
(if (empty? C)
(->> A reverse (take 2) (into []))
(->> (for [c C] [c (reduce + (for [a A] (get G #{c a} 0)))])
(sort-by second)
(last
first
conj A)
(recur))))))
#'stoer-wagner/find-minimum-cut
Let us try on G:
(find-minimum-cut G)
8 7] [
Next is the function that does the merging of vertices:
defn shrink-graph [G a b]
(let [H (filter #(empty? (intersection #{a b} %))
(keys G))
(for [e (remove #{#{a b}} (keys G))
K (:when (not (empty? (intersection e #{a b})))
:let [c (->> e (remove #{a b}) first)]]
+ (get G #{c a} 0) (get G #{c b} 0))})]
{#{(helper a b) c} (merge-with + (into {} K) (select-keys G H)))) (
#'stoer-wagner/shrink-graph
Let us test again:
apply shrink-graph G (find-minimum-cut G)) (
4 3} 4, #{7 6} 1, #{7 3} 2, #{4 8} 2, #{6 #{1 5}} 3, #{#{1 5} 2} 4, #{7 4} 2, #{6 2} 2, #{7 8} 3, #{3 2} 3} {#{
And finally, the main engine of the algorithm:
defn stoer-wagner [G]
(loop [x #{}
(reduce + (vals G))
w (
H G]if (= 1 (count H))
(let [A (helper x #{})
(reduce union (keys G))]
V (difference V A)])
[w A (let [[a b] (find-minimum-cut H)
(
wn (calculate-cut-weight H a)
Hn (shrink-graph H a b)]if (< wn w)
(recur a wn Hn)
(recur x w Hn)))))) (
#'stoer-wagner/stoer-wagner
Let us see what it does on G
(stoer-wagner G)
4 #{1 6 2 5} #{7 4 3 8}] [