Skip to content
This repository has been archived by the owner on Oct 8, 2021. It is now read-only.

Time and memory efficient Tarjan's algorithm for strongly connected components in a directed graph #1182

Merged
merged 10 commits into from
Mar 27, 2019
64 changes: 51 additions & 13 deletions src/connectivity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ true
"""
is_weakly_connected(g) = is_connected(g)




Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need all this whitespace here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

"""
strongly_connected_components(g)

Expand All @@ -196,25 +199,59 @@ julia> strongly_connected_components(g)
2-element Array{Array{Int64,1},1}:
[3]
[1, 2]


julia> g=SimpleDiGraph(11)
{11, 0} directed simple Int64 graph

julia> edge_list=[(1,2),(2,3),(3,4),(4,1),(3,5),(5,6),(6,7),(7,5),(5,8),(8,9),(9,8),(10,11),(11,10)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you write julia> edge_list=[(1,2),(2,3),(3,4),(4,1),(3,5),(5,6),(6,7),(7,5),(5,8),(8,9),(9,8),(10,11),(11,10)]; (with a semicolon instead), then you won't get any output, so maybe we can shorten the unnecessary long output in this example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

13-element Array{Tuple{Int64,Int64},1}:
(1, 2)
(2, 3)
(3, 4)
(4, 1)
(3, 5)
(5, 6)
(6, 7)
(7, 5)
(5, 8)
(8, 9)
(9, 8)
(10, 11)
(11, 10)

julia> g = SimpleDiGraph(Edge.(edge_list))
{11, 13} directed simple Int64 graph

julia> strongly_connected_components_kosaraju(g)
sinhatushar marked this conversation as resolved.
Show resolved Hide resolved
4-element Array{Array{Int64,1},1}:
[8, 9]
[5, 6, 7]
[1, 2, 3, 4]
[10, 11]

```
"""

sinhatushar marked this conversation as resolved.
Show resolved Hide resolved
function strongly_connected_components end
# see https://github.com/mauro3/SimpleTraits.jl/issues/47#issuecomment-327880153 for syntax
@traitfn function strongly_connected_components(g::AG::IsDirected) where {T, AG <: AbstractGraph{T}}
@traitfn function strongly_connected_components(g::AG::IsDirected) where {T<:Integer, AG <: AbstractGraph{T}}
zero_t = zero(T)
one_t = one(T)
nvg = nv(g)
count = one_t

index = zeros(T, nvg) # first time in which vertex is discovered
stack = Vector{T}() # stores vertices which have been discovered and not yet assigned to any component
stack = Vector{T}() # stores vertices which have been discovered and not yet assigned to any component
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indent not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

onstack = zeros(Bool, nvg) # false if a vertex is waiting in the stack to receive a component assignment
lowlink = zeros(T, nvg) # lowest index vertex that it can reach through back edge (index array not vertex id number)
parents = zeros(T, nvg) # parent of every vertex in dfs
components = Vector{Vector{T}}() # maintains a list of scc (order is not guaranteed in API)
sizehint!(stack, nvg)
sizehint!(components, nvg)

for s in vertices(g)

dfs_stack = Vector{T}()

@inbounds for s in vertices(g)
if index[s] == zero_t
index[s] = count
lowlink[s] = count
Expand All @@ -224,21 +261,21 @@ function strongly_connected_components end
count = count + one_t

# start dfs from 's'
dfs_stack = Vector{T}([s])
push!(dfs_stack,s)
while !isempty(dfs_stack)
v = dfs_stack[end] #end is the most recently added item
u = zero_t
for n in outneighbors(g, v)
if index[n] == zero_t
@inbounds for v_neighbor in outneighbors(g, v)
if index[v_neighbor] == zero_t
# unvisited neighbor found
u = n
u = v_neighbor
break
#GOTO A push u onto DFS stack and continue DFS
elseif onstack[n]
elseif onstack[v_neighbor]
# we have already seen n, but can update the lowlink of v
# which has the effect of possibly keeping v on the stack until n is ready to pop.
# update lowest index 'v' can reach through out neighbors
lowlink[v] = min(lowlink[v], index[n])
lowlink[v] = min(lowlink[v], index[v_neighbor])
end
end
if u == zero_t
Expand All @@ -250,7 +287,7 @@ function strongly_connected_components end
if index[v] == lowlink[v]
# found a cycle in a completed dfs tree.
component = Vector{T}()
sizehint!(component, length(stack))

while !isempty(stack) #break when popped == v
# drain stack until we see v.
# everything on the stack until we see v is in the SCC rooted at v.
Expand All @@ -263,7 +300,7 @@ function strongly_connected_components end
break
end
end
push!(components, reverse(component))
push!(components, reverse!(component))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of a hack. It relies on the fact that reverse! returns the modified vector, but strictly speaking, it doesn't HAVE to since it's a mutating function. Better to call reverse! component on the line before and use component here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

end
else #LABEL A
# add unvisited neighbor to dfs
Expand All @@ -284,6 +321,7 @@ function strongly_connected_components end
end



"""
is_strongly_connected(g)

Expand Down