Skip to content

Commit 07ea83a

Browse files
committed
Merge pull request JuliaAttic#176 from JuliaGraphs/issue/JuliaAttic#162
Replace the inefficient bfs_tree
2 parents 18b630d + b040e92 commit 07ea83a

File tree

4 files changed

+130
-84
lines changed

4 files changed

+130
-84
lines changed

src/connectivity.jl

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,6 @@
11
# Parts of this code were taken / derived from Graphs.jl. See LICENSE for
22
# licensing details.
33

4-
"""Returns the [connected components](https://en.wikipedia.org/wiki/Connectivity_(graph_theory))
5-
of an undirected graph `g` as a vector of components, each represented by a
6-
vector of vectors of vertices belonging to the component.
7-
"""
8-
#= function connected_components(g::Graph) =#
9-
#= nvg = nv(g) =#
10-
#= found = zeros(Bool, nvg) =#
11-
#= components = @compat Vector{Vector{Int}}() =#
12-
#= for v in 1:nvg =#
13-
#= if !found[v] =#
14-
#= bfstree = bfs_tree(g, v) =#
15-
#= found_vertices = @compat Vector{Int}() =#
16-
#= for e in edges(bfstree) =#
17-
#= push!(found_vertices, src(e)) =#
18-
#= push!(found_vertices, dst(e)) =#
19-
#= end =#
20-
#= found_vertices = unique(found_vertices) =#
21-
#= found[found_vertices] = true =#
22-
#= if length(found_vertices) > 0 =#
23-
#= push!(components, found_vertices) =#
24-
#= end =#
25-
#= end =#
26-
#= end =#
27-
#= return components =#
28-
#= end =#
29-
30-
function connected_components!(visitor::TreeBFSVisitorVector, g::Graph)
31-
nvg = nv(g)
32-
found = zeros(Bool, nvg)
33-
components = @compat Vector{Vector{Int}}()
34-
for v in 1:nvg
35-
if !found[v]
36-
fill!(visitor.tree, 0)
37-
visitor.tree[v] = v
38-
parents = bfs_tree!(visitor, g, v)
39-
found_vertices = @compat Vector{Int}()
40-
for i in 1:nvg
41-
if parents[i] > 0
42-
push!(found_vertices, i)
43-
end
44-
end
45-
found[found_vertices] = true
46-
if length(found_vertices) > 0
47-
push!(components, found_vertices)
48-
end
49-
end
50-
end
51-
return components
52-
end
534

545
"""connected_components! produces a label array of components
556
@@ -61,6 +12,13 @@ Output:
6112
c is the smallest vertex id in the component.
6213
"""
6314
function connected_components!(label::Vector{Int}, g::Graph)
15+
# this version of connected components uses Breadth First Traversal
16+
# with custom visitor type in order to improve performance.
17+
# one BFS is performed for each component.
18+
# This algorithm is linear in the number of edges of the graph
19+
# each edge is touched once. memory performance is a single allocation.
20+
# the return type is a vector of labels which can be used directly or
21+
# passed to components(a)
6422
nvg = nv(g)
6523
visitor = LightGraphs.ComponentVisitorVector(label, 0)
6624
colormap = zeros(Int,nvg)
@@ -120,6 +78,10 @@ function components(labels::Vector{Int})
12078
return c, d
12179
end
12280

81+
"""Returns the [connected components](https://en.wikipedia.org/wiki/Connectivity_(graph_theory))
82+
of an undirected graph `g` as a vector of components, each represented by a
83+
vector of vectors of vertices belonging to the component.
84+
"""
12385
function connected_components(g)
12486
label = zeros(Int, nv(g))
12587
connected_components!(label, g)

src/traversals/bfs.jl

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -124,37 +124,64 @@ function gdistances(graph::SimpleGraph, sources; defaultdist::Int=-1)
124124
gdistances!(graph, sources, dists)
125125
end
126126

127+
###########################################
128+
# Constructing BFS trees #
129+
###########################################
130+
131+
# this type has been deprecated in favor of TreeBFSVisitorVector and the tree function.
132+
"""TreeBFSVisitor is a type for representing a BFS traversal of the graph as a DiGraph"""
127133
type TreeBFSVisitor <:SimpleGraphVisitor
128134
tree::DiGraph
129135
end
130136

137+
TreeBFSVisitor(n::Int) = TreeBFSVisitor(DiGraph(n))
138+
139+
@deprecate TreeBFSVisitor(x) TreeBFSVisitorVector(x)
140+
141+
"""TreeBFSVisitorVector is a type for representing a BFS traversal
142+
of the graph as a parents array. This type allows for a more performant implementation.
143+
"""
131144
type TreeBFSVisitorVector <: SimpleGraphVisitor
132145
tree::Vector{Int}
133146
end
134147

135-
type ComponentVisitorVector <: SimpleGraphVisitor
136-
labels::Vector{Int}
137-
seed::Int
148+
function TreeBFSVisitorVector(n::Int)
149+
return TreeBFSVisitorVector(zeros(Int, n))
138150
end
139-
function examine_neighbor!(visitor::TreeBFSVisitorVector, u::Int, v::Int, vcolor::Int, ecolor::Int)
140-
# println("discovering $u -> $v, vcolor = $vcolor, ecolor = $ecolor")
141-
if u != v && vcolor == 0
142-
visitor.tree[v] = u
151+
152+
"""TreeBFSVisitor converts a parents array into a DiGraph"""
153+
function TreeBFSVisitor(tvv::TreeBFSVisitorVector)
154+
n = length(tvv.tree)
155+
parents = tvv.tree
156+
g = tree(parents)
157+
return TreeBFSVisitor(g)
158+
end
159+
160+
"""tree converts a parents array into a DiGraph"""
161+
function tree(parents::AbstractVector)
162+
n = length(parents)
163+
t = DiGraph(n)
164+
for i in 1:n
165+
parent = parents[i]
166+
if parent > 0 && parent != i
167+
add_edge!(t, parent, i)
168+
end
143169
end
144-
return true
170+
return t
145171
end
146172

147-
function examine_neighbor!(visitor::ComponentVisitorVector, u::Int, v::Int, vcolor::Int, ecolor::Int)
173+
tree(parents::TreeBFSVisitorVector) = tree(parents.tree)
174+
175+
function examine_neighbor!(visitor::TreeBFSVisitorVector, u::Int, v::Int, vcolor::Int, ecolor::Int)
148176
# println("discovering $u -> $v, vcolor = $vcolor, ecolor = $ecolor")
149177
if u != v && vcolor == 0
150-
visitor.labels[v] = visitor.seed
178+
visitor.tree[v] = u
151179
end
152180
return true
153181
end
154-
# Return the DAG representing the traversal of a graph.
155182

156-
TreeBFSVisitor(n::Int) = TreeBFSVisitor(DiGraph(n))
157183

184+
# Return the DAG representing the traversal of a graph.
158185
function examine_neighbor!(visitor::TreeBFSVisitor, u::Int, v::Int, vcolor::Int, ecolor::Int)
159186
# println("discovering $u -> $v, vcolor = $vcolor, ecolor = $ecolor")
160187
if u != v && vcolor == 0
@@ -163,34 +190,58 @@ function examine_neighbor!(visitor::TreeBFSVisitor, u::Int, v::Int, vcolor::Int,
163190
return true
164191
end
165192

166-
"""Provides a breadth-first traversal of the graph `g` starting with source vertex `s`,
167-
and returns a directed acyclic graph of vertices in the order they were discovered.
168-
"""
169-
function bfs_tree(g::SimpleGraph, s::Int)
170-
nvg = nv(g)
171-
visitor = TreeBFSVisitor(nvg)
172-
traverse_graph(g, BreadthFirst(), s, visitor)
173-
return visitor.tree
174-
end
175193

176-
function bfs_tree(visitor::TreeBFSVisitorVector, g::SimpleGraph, s::Int)
177-
nvg = nv(g)
178-
visitor = TreeBFSVisitorVector(zeros(Int,nvg))
179-
visitor.tree[s] = s
180-
return bfs_tree!(visitor, g, s)
181-
end
182194

183195
function bfs_tree!(visitor::TreeBFSVisitorVector,
184196
g::SimpleGraph,
185197
s::Int;
186198
colormap=zeros(Int, nv(g)),
187199
que=Vector{Int}())
200+
# this version of bfs_tree! allows one to reuse the memory necessary to compute the tree
201+
# the output is stored in the visitor.tree array whose entries are the vertex id of the
202+
# parent of the index. This function checks if the scratch space is too small for the graph.
203+
# and throws an error if it is too small.
204+
# the source is represented in the output by a fixed point v[root] == root.
205+
# this function is considered a performant version of bfs_tree for useful when the parent
206+
# array is more helpful than a DiGraph struct, or when performance is critical.
207+
nvg = nv(g)
208+
length(visitor.tree) >= nvg || error("visitor.tree too small for graph")
209+
visitor.tree[s] = s
188210
traverse_graph(g, BreadthFirst(), s, visitor; colormap=colormap, que=que)
189-
return visitor.tree
190211
end
191212

192-
# Test graph for bipartiteness
213+
"""Provides a breadth-first traversal of the graph `g` starting with source vertex `s`,
214+
and returns a directed acyclic graph of vertices in the order they were discovered.
215+
216+
This function is a high level wrapper around bfs_tree!, use that function for more performance.
217+
"""
218+
function bfs_tree(g::SimpleGraph, s::Int)
219+
nvg = nv(g)
220+
visitor = TreeBFSVisitorVector(nvg)
221+
bfs_tree!(visitor, g, s)
222+
return tree(visitor)
223+
end
224+
225+
############################################
226+
# Connected Components with BFS #
227+
############################################
228+
"""Performing connected components with BFS starting from seed"""
229+
type ComponentVisitorVector <: SimpleGraphVisitor
230+
labels::Vector{Int}
231+
seed::Int
232+
end
233+
234+
function examine_neighbor!(visitor::ComponentVisitorVector, u::Int, v::Int, vcolor::Int, ecolor::Int)
235+
# println("discovering $u -> $v, vcolor = $vcolor, ecolor = $ecolor")
236+
if u != v && vcolor == 0
237+
visitor.labels[v] = visitor.seed
238+
end
239+
return true
240+
end
193241

242+
############################################
243+
# Test graph for bipartiteness #
244+
############################################
194245
type BipartiteVisitor <: SimpleGraphVisitor
195246
bipartitemap::Vector{UInt8}
196247
is_bipartite::Bool

test/connectivity.jl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@ add_edge!(g,10,9)
1010
@test is_connected(HouseGraph())
1111

1212
cc = connected_components(g)
13-
visitor = LightGraphs.TreeBFSVisitorVector(zeros(Int, nv(g)))
1413
label = zeros(Int, nv(g))
15-
label = LightGraphs.connected_components!(label, g)
16-
ccfast = LightGraphs.connected_components!(visitor, g)
14+
LightGraphs.connected_components!(label, g)
1715
@test label[1:10] == [1,1,1,1,5,5,5,8,8,8]
18-
@test ccfast[1:3] == map(sort, cc)[1:3]
1916
import LightGraphs: components, components_dict
2017
cclab = components_dict(label)
2118
@test cclab[1] == [1,2,3,4]
@@ -25,7 +22,6 @@ cclab = components_dict(label)
2522

2623
@test length(cc) >= 3 && sort(cc[3]) == [8,9,10]
2724

28-
2925
# graph from https://en.wikipedia.org/wiki/Strongly_connected_component
3026
h = DiGraph(8)
3127
add_edge!(h,1,2); add_edge!(h,2,3); add_edge!(h,2,5);

test/traversals/bfs.jl

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
z = bfs_tree(g5, 1)
22
visitor = LightGraphs.TreeBFSVisitorVector(zeros(Int,nv(g5)))
3-
t = LightGraphs.bfs_tree(visitor, g5, 1)
3+
LightGraphs.bfs_tree!(visitor, g5, 1)
4+
t = visitor.tree
45
@test nv(z) == 4 && ne(z) == 3 && !has_edge(z, 2, 3)
56
@test t == [1,1,1,3]
67

@@ -14,3 +15,39 @@ add_edge!(g,1,2); add_edge!(g,1,4)
1415
add_edge!(g,2,3); add_edge!(g,2,5)
1516
add_edge!(g,3,4)
1617
@test is_bipartite(g)
18+
19+
20+
import LightGraphs: TreeBFSVisitorVector, bfs_tree!, TreeBFSVisitor, tree
21+
g = HouseGraph()
22+
n = nv(g)
23+
visitor = TreeBFSVisitorVector(n)
24+
@test length(visitor.tree) == n
25+
parents = visitor.tree
26+
bfs_tree!(visitor, g, 1)
27+
maxdepth = n
28+
29+
function istree(parents::Vector{Int})
30+
flag = true
31+
for i in 1:n
32+
s = i
33+
depth = 0
34+
while parents[s] > 0 && parents[s] != s
35+
s = parents[s]
36+
depth += 1
37+
if depth > maxdepth
38+
return false
39+
end
40+
end
41+
end
42+
return flag
43+
end
44+
45+
@test istree(parents) == true
46+
tvis = TreeBFSVisitor(visitor)
47+
@test nv(tvis.tree) == nv(g)
48+
@test typeof(tvis.tree) <: DiGraph
49+
t = tree(parents)
50+
@test typeof(t) <: DiGraph
51+
@test typeof(tvis.tree) <: DiGraph
52+
@test t == tvis.tree
53+
@test ne(t) < nv(t)

0 commit comments

Comments
 (0)