### Tarjan’s strongly connected components algorithm

I dislike algorithms that are full of indices and mutations. Not because they are bad but because I always have the feeling that the core idea is buried. As such, Tarjan’s SCC algorithm irked me.

So I took the traditional algorithm, implemented it in Clojure with explicit environment passing, then I replaced indices by explicit stacks (thanks to persistence!) and after some tweaks, I realized that I’ve gone full circle and could switch to stacks lengths instead of stacks themselves and get rid of the loop. However the whole process made the code cleaner to my eye. You can look at the whole history.

Here is the resulting code:

(defn tarjan "Returns the strongly connected components of a graph specified by its nodes and a successor function succs from node to nodes. The used algorithm is Tarjan's one." [nodes succs] (letfn [(sc [env node] ; env is a map from nodes to stack length or nil, ; nil means the node is known to belong to another SCC ; there are two special keys: ::stack for the current stack ; and ::sccs for the current set of SCCs (if (contains? env node) env (let [stack (::stack env) n (count stack) env (assoc env node n ::stack (conj stack node)) env (reduce (fn [env succ] (let [env (sc env succ)] (assoc env node (min (or (env succ) n) (env node))))) env (succs node))] (if (= n (env node)) ; no link below us in the stack, call it a SCC (let [nodes (::stack env) scc (set (take (- (count nodes) n) nodes)) ; clear all stack lengths for these nodes since this SCC is done env (reduce #(assoc %1 %2 nil) env scc)] (assoc env ::stack stack ::sccs (conj (::sccs env) scc))) env))))] (::sccs (reduce sc {::stack () ::sccs #{}} nodes))))

As always, if you need some short-term help with Clojure (code review, consulting, training etc.), contact me!

Hi,

Interesting article as always!

The link to “Tarjanâ€™s SCC algorithm” is broken. I guess you meant to use http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm .

– Max

@Max link fixed, thanks!

Any example usage? like if I invoked it like this: (tarjan {:a [:b :c] :b [:c] :c [:a] :d [:e] :e [:a]} :e) …is it right?

One of the nice things about Tarjan’s algorithm is that its output is topologically sorted. Would it be possible to change yours to achieve that as well?

I suspect a bug.

Testcase: (tarjan [:a :b :c] {:a #{:b :c} :b #{:a} :c #{:a}})

Expected: #{#{:a :b} #{:a :c}}

Result: #{#{:c :b :a}}

My mistake, no bug…