|
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 | +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 | +
|
10 | 14 | ### Performance |
11 | 15 | This algorithm is linear in the number of edges of the graph. |
12 | 16 | """ |
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") |
14 | 21 | for u in vertices(g) |
15 | 22 | label[u] != zero(T) && continue |
16 | 23 | 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) |
21 | 27 | for vertex in all_neighbors(g, src) |
22 | 28 | if label[vertex] == zero(T) |
23 | | - push!(Q, vertex) |
| 29 | + push!(search_queue, vertex) |
24 | 30 | label[vertex] = u |
25 | 31 | end |
26 | 32 | end |
@@ -129,9 +135,61 @@ julia> is_connected(g) |
129 | 135 | true |
130 | 136 | ``` |
131 | 137 | """ |
132 | | -function is_connected(g::AbstractGraph) |
| 138 | +function is_connected(g::AbstractGraph{T}) where T |
133 | 139 | 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 |
135 | 193 | end |
136 | 194 |
|
137 | 195 | """ |
|
0 commit comments