|
1 | 1 | # Parts of this code were taken / derived from Graphs.jl. See LICENSE for |
2 | 2 | # licensing details. |
3 | 3 | """ |
4 | | - connected_components!(label, g) |
| 4 | + connected_components!(label, g, [search_queue]) |
5 | 5 |
|
6 | 6 | Fill `label` with the `id` of the connected component in the undirected graph |
7 | 7 | `g` to which it belongs. Return a vector representing the component assigned |
8 | 8 | to each vertex. The component value is the smallest vertex ID in the component. |
9 | 9 |
|
10 | | -### Performance |
| 10 | +## Optional arguments |
| 11 | +- `search_queue`, an empty `Vector{eltype(edgetype(g))}`, can be provided to avoid |
| 12 | + reallocating this work array repeatedly on repeated calls of `connected_components!`. |
| 13 | + If not provided, it is automatically instantiated. |
| 14 | +
|
| 15 | +!!! warning "Experimental" |
| 16 | + The `search_queue` argument is experimental and subject to potential change |
| 17 | + in future versions of Graphs.jl. |
| 18 | +
|
| 19 | +## Performance |
11 | 20 | This algorithm is linear in the number of edges of the graph. |
12 | 21 | """ |
13 | | -function connected_components!(label::AbstractVector, g::AbstractGraph{T}) where {T} |
| 22 | +function connected_components!( |
| 23 | + label::AbstractVector{T}, g::AbstractGraph{T}, search_queue::Vector{T}=Vector{T}() |
| 24 | +) where {T} |
| 25 | + empty!(search_queue) |
14 | 26 | for u in vertices(g) |
15 | 27 | label[u] != zero(T) && continue |
16 | 28 | label[u] = u |
17 | | - Q = Vector{T}() |
18 | | - push!(Q, u) |
19 | | - while !isempty(Q) |
20 | | - src = popfirst!(Q) |
| 29 | + push!(search_queue, u) |
| 30 | + while !isempty(search_queue) |
| 31 | + src = popfirst!(search_queue) |
21 | 32 | for vertex in all_neighbors(g, src) |
22 | 33 | if label[vertex] == zero(T) |
23 | | - push!(Q, vertex) |
| 34 | + push!(search_queue, vertex) |
24 | 35 | label[vertex] = u |
25 | 36 | end |
26 | 37 | end |
@@ -129,9 +140,78 @@ julia> is_connected(g) |
129 | 140 | true |
130 | 141 | ``` |
131 | 142 | """ |
132 | | -function is_connected(g::AbstractGraph) |
| 143 | +function is_connected(g::AbstractGraph{T}) where {T} |
133 | 144 | mult = is_directed(g) ? 2 : 1 |
134 | | - return mult * ne(g) + 1 >= nv(g) && length(connected_components(g)) == 1 |
| 145 | + if mult * ne(g) + 1 >= nv(g) |
| 146 | + label = zeros(T, nv(g)) |
| 147 | + connected_components!(label, g) |
| 148 | + return allequal(label) |
| 149 | + else |
| 150 | + return false |
| 151 | + end |
| 152 | +end |
| 153 | + |
| 154 | +""" |
| 155 | + count_connected_components( g, [label, search_queue]; reset_label::Bool=false) |
| 156 | +
|
| 157 | +Return the number of connected components in `g`. |
| 158 | +
|
| 159 | +Equivalent to `length(connected_components(g))` but uses fewer allocations by not |
| 160 | +materializing the component vectors explicitly. |
| 161 | +
|
| 162 | +## Optional arguments |
| 163 | +Mutated work arrays, `label` and `search_queue` can be provided to avoid allocating these |
| 164 | +arrays repeatedly on repeated calls of `count_connected_components`. |
| 165 | +For `g :: AbstractGraph{T}`, `label` must be a zero-initialized `Vector{T}` of length |
| 166 | +`nv(g)` and `search_queue` a `Vector{T}`. See also [`connected_components!`](@ref). |
| 167 | +
|
| 168 | +!!! warning "Experimental" |
| 169 | + The `search_queue` and `label` arguments are experimental and subject to potential |
| 170 | + change in future versions of Graphs.jl. |
| 171 | +
|
| 172 | +## Keyword arguments |
| 173 | +- `reset_label :: Bool` (default, `false`): if `true`, `label` is reset to a zero-vector |
| 174 | + before returning. |
| 175 | +
|
| 176 | +## Example |
| 177 | +``` |
| 178 | +julia> using Graphs |
| 179 | +
|
| 180 | +julia> g = Graph(Edge.([1=>2, 2=>3, 3=>1, 4=>5, 5=>6, 6=>4, 7=>8])); |
| 181 | +
|
| 182 | +length> connected_components(g) |
| 183 | +3-element Vector{Vector{Int64}}: |
| 184 | + [1, 2, 3] |
| 185 | + [4, 5, 6] |
| 186 | + [7, 8] |
| 187 | +
|
| 188 | +julia> count_connected_components(g) |
| 189 | +3 |
| 190 | +``` |
| 191 | +""" |
| 192 | +function count_connected_components( |
| 193 | + g::AbstractGraph{T}, |
| 194 | + label::AbstractVector{T}=zeros(T, nv(g)), |
| 195 | + search_queue::Vector{T}=Vector{T}(); |
| 196 | + reset_label::Bool=false, |
| 197 | +) where {T} |
| 198 | + connected_components!(label, g, search_queue) |
| 199 | + c = count_unique(label) |
| 200 | + reset_label && fill!(label, zero(eltype(label))) |
| 201 | + return c |
| 202 | +end |
| 203 | + |
| 204 | +function count_unique(label::Vector{T}) where {T} |
| 205 | + # effectively does `length(Set(label))` but faster, since `Set(label)` sizehints |
| 206 | + # aggressively and assumes that most elements of `label` will be unique, which very |
| 207 | + # rarely will be the case for caller `count_connected_components!` |
| 208 | + seen = T === Int ? BitSet() : Set{T}() # if `T=Int`, we can use faster BitSet |
| 209 | + for l in label |
| 210 | + # faster than direct `push!(seen, l)` when `label` has few unique elements relative |
| 211 | + # to `length(label)` |
| 212 | + l ∉ seen && push!(seen, l) |
| 213 | + end |
| 214 | + return length(seen) |
135 | 215 | end |
136 | 216 |
|
137 | 217 | """ |
|
0 commit comments