Skip to content

Commit 59270de

Browse files
committed
implement count_connected_components(g) as faster version of length(connected_components(g))
1 parent 24539fd commit 59270de

File tree

3 files changed

+74
-15
lines changed

3 files changed

+74
-15
lines changed

src/Graphs.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ export
210210

211211
# connectivity
212212
connected_components,
213+
count_connected_components,
213214
strongly_connected_components,
214215
strongly_connected_components_kosaraju,
215216
strongly_connected_components_tarjan,

src/connectivity.jl

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
11
# Parts of this code were taken / derived from Graphs.jl. See LICENSE for
22
# licensing details.
33
"""
4-
connected_components!(label, g)
4+
connected_components!(label, g, [search_queue])
55
66
Fill `label` with the `id` of the connected component in the undirected graph
77
`g` to which it belongs. Return a vector representing the component assigned
88
to each vertex. The component value is the smallest vertex ID in the component.
99
10+
A `search_queue`, an empty `Vector{eltype(edgetype(g))}`, can be provided to reduce
11+
allocations if `connected_components!` is intended to be called multiple times sequentially.
12+
If not provided, it is automatically instantiated.
13+
1014
### Performance
1115
This algorithm is linear in the number of edges of the graph.
1216
"""
13-
function connected_components!(label::AbstractVector, g::AbstractGraph{T}) where {T}
17+
function connected_components!(
18+
label::AbstractVector, g::AbstractGraph{T}, search_queue::Vector{T}=Vector{T}()
19+
) where {T}
20+
isempty(search_queue) || error("provided `search_queue` is not empty")
1421
for u in vertices(g)
1522
label[u] != zero(T) && continue
1623
label[u] = u
17-
Q = Vector{T}()
18-
push!(Q, u)
19-
while !isempty(Q)
20-
src = popfirst!(Q)
24+
push!(search_queue, u)
25+
while !isempty(search_queue)
26+
src = popfirst!(search_queue)
2127
for vertex in all_neighbors(g, src)
2228
if label[vertex] == zero(T)
23-
push!(Q, vertex)
29+
push!(search_queue, vertex)
2430
label[vertex] = u
2531
end
2632
end
@@ -129,9 +135,61 @@ julia> is_connected(g)
129135
true
130136
```
131137
"""
132-
function is_connected(g::AbstractGraph)
138+
function is_connected(g::AbstractGraph{T}) where T
133139
mult = is_directed(g) ? 2 : 1
134-
return mult * ne(g) + 1 >= nv(g) && length(connected_components(g)) == 1
140+
if mult * ne(g) + 1 >= nv(g)
141+
label = zeros(T, nv(g))
142+
connected_components!(label, g)
143+
return allequal(label)
144+
else
145+
return false
146+
end
147+
end
148+
149+
"""
150+
count_connected_components( g, [label, search_queue])
151+
152+
Return the number of connected components in `g`.
153+
154+
Equivalent to `length(connected_components(g))` but uses fewer allocations by not
155+
materializing the component vectors explicitly. Additionally, mutated work-arrays `label`
156+
and `search_queue` can be provided to reduce allocations further (see
157+
[`connected_components!`](@ref)).
158+
159+
```
160+
julia> using Graphs
161+
162+
julia> g = Graph(Edge.([1=>2, 2=>3, 3=>1, 4=>5, 5=>6, 6=>4, 7=>8]));
163+
164+
length> connected_components(g)
165+
3-element Vector{Vector{Int64}}:
166+
[1, 2, 3]
167+
[4, 5, 6]
168+
[7, 8]
169+
170+
julia> count_connected_components(g)
171+
3
172+
```
173+
"""
174+
function count_connected_components(
175+
g::AbstractGraph{T},
176+
label::AbstractVector=zeros(T, nv(g)),
177+
search_queue::Vector{T}=Vector{T}()
178+
) where T
179+
connected_components!(label, g, search_queue)
180+
return count_unique(label)
181+
end
182+
183+
function count_unique(label::Vector{T}) where T
184+
seen = Set{T}()
185+
c = 0
186+
for l in label
187+
if l seen
188+
push!(seen, l)
189+
c += 1
190+
end
191+
end
192+
return c
135193
end
136194

137195
"""

test/spanningtrees/boruvka.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121
g1t = GenericGraph(SimpleGraph(edges1))
2222
@test res1.weight == cost_mst
2323
# acyclic graphs have n - c edges
24-
@test nv(g1t) - length(connected_components(g1t)) == ne(g1t)
24+
@test nv(g1t) - ne(g1t) == length(connected_components(g1t)) == count_connected_components(g1t)
2525
@test nv(g1t) == nv(g)
2626

2727
res2 = boruvka_mst(g, distmx; minimize=false)
2828
edges2 = [Edge(src(e), dst(e)) for e in res2.mst]
2929
g2t = GenericGraph(SimpleGraph(edges2))
3030
@test res2.weight == cost_max_vec_mst
31-
@test nv(g2t) - length(connected_components(g2t)) == ne(g2t)
31+
@test nv(g2t) - ne(g2t) == length(connected_components(g2t)) == count_connected_components(g2t)
3232
@test nv(g2t) == nv(g)
3333
end
3434
# second test
@@ -60,14 +60,14 @@
6060
edges3 = [Edge(src(e), dst(e)) for e in res3.mst]
6161
g3t = GenericGraph(SimpleGraph(edges3))
6262
@test res3.weight == weight_vec2
63-
@test nv(g3t) - length(connected_components(g3t)) == ne(g3t)
63+
@test nv(g3t) - ne(g3t) == length(connected_components(g3t)) == count_connected_components(g3t)
6464
@test nv(g3t) == nv(gx)
6565

6666
res4 = boruvka_mst(g, distmx_sec; minimize=false)
6767
edges4 = [Edge(src(e), dst(e)) for e in res4.mst]
6868
g4t = GenericGraph(SimpleGraph(edges4))
6969
@test res4.weight == weight_max_vec2
70-
@test nv(g4t) - length(connected_components(g4t)) == ne(g4t)
70+
@test nv(g4t) - ne(g4t) == length(connected_components(g4t)) == count_connected_components(g4t)
7171
@test nv(g4t) == nv(gx)
7272
end
7373

@@ -123,14 +123,14 @@
123123
edges5 = [Edge(src(e), dst(e)) for e in res5.mst]
124124
g5t = GenericGraph(SimpleGraph(edges5))
125125
@test res5.weight == weight_vec3
126-
@test nv(g5t) - length(connected_components(g5t)) == ne(g5t)
126+
@test nv(g5t) - ne(g5t) == length(connected_components(g5t)) == count_connected_components(g5t)
127127
@test nv(g5t) == nv(gd)
128128

129129
res6 = boruvka_mst(g, distmx_third; minimize=false)
130130
edges6 = [Edge(src(e), dst(e)) for e in res6.mst]
131131
g6t = GenericGraph(SimpleGraph(edges6))
132132
@test res6.weight == weight_max_vec3
133-
@test nv(g6t) - length(connected_components(g6t)) == ne(g6t)
133+
@test nv(g6t) - ne(g6t) == length(connected_components(g6t)) == count_connected_components(g6t)
134134
@test nv(g6t) == nv(gd)
135135
end
136136
end

0 commit comments

Comments
 (0)