-
Notifications
You must be signed in to change notification settings - Fork 122
Implement is_articulation to check if vertex is articulation point
#387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+138
−58
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
f7f3247
factor out dfs-search from and implement using this
thchr 9a3c370
fix typo and add test for unconnected graph
thchr 1e41e01
format w/ JuliaFormatter
thchr 870597c
bump to v1.11.2
thchr 669093e
docstring nits
thchr 00f4843
Apply suggestions from code review
thchr 5a5cddc
Merge branch 'master' into is-cut-vertex
Krastanov c3ff4ac
Run formatter and update changelog for PR 387
Krastanov 96f3eaa
Merge branch 'master' into is-cut-vertex
Krastanov 098ef79
move changelog to unreleased section
Krastanov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| """ | ||
| articulation(g) | ||
|
|
||
| Compute the [articulation points](https://en.wikipedia.org/wiki/Biconnected_component) | ||
| of a connected graph `g` and return an array containing all cut vertices. | ||
| Compute the [articulation points](https://en.wikipedia.org/wiki/Biconnected_component) (also | ||
| known as cut or seperating vertices) of an undirected graph `g` and return a vector | ||
| containing all the vertices of `g` that are articulation points. | ||
|
|
||
| # Examples | ||
| ```jldoctest | ||
|
|
@@ -22,74 +23,136 @@ julia> articulation(path_graph(5)) | |
| function articulation end | ||
| @traitfn function articulation(g::AG::(!IsDirected)) where {T,AG<:AbstractGraph{T}} | ||
| s = Vector{Tuple{T,T,T}}() | ||
| is_articulation_pt = falses(nv(g)) | ||
| low = zeros(T, nv(g)) | ||
| pre = zeros(T, nv(g)) | ||
|
|
||
| is_articulation_pt = falses(nv(g)) | ||
| @inbounds for u in vertices(g) | ||
| pre[u] != 0 && continue | ||
| v = u | ||
| children = 0 | ||
| wi::T = zero(T) | ||
| w::T = zero(T) | ||
| cnt::T = one(T) | ||
| first_time = true | ||
|
|
||
| # TODO the algorithm currently relies on the assumption that | ||
| # outneighbors(g, v) is indexable. This assumption might not be true | ||
| # in general, so in case that outneighbors does not produce a vector | ||
| # we collect these vertices. This might lead to a large number of | ||
| # allocations, so we should find a way to handle that case differently, | ||
| # or require inneighbors, outneighbors and neighbors to always | ||
| # return indexable collections. | ||
|
|
||
| while !isempty(s) || first_time | ||
| first_time = false | ||
| if wi < 1 | ||
| pre[v] = cnt | ||
| cnt += 1 | ||
| low[v] = pre[v] | ||
| v_neighbors = collect_if_not_vector(outneighbors(g, v)) | ||
| wi = 1 | ||
| else | ||
| wi, u, v = pop!(s) | ||
| v_neighbors = collect_if_not_vector(outneighbors(g, v)) | ||
| w = v_neighbors[wi] | ||
| low[v] = min(low[v], low[w]) | ||
| if low[w] >= pre[v] && u != v | ||
| articulation_dfs!(is_articulation_pt, g, u, s, low, pre) | ||
| end | ||
|
|
||
| articulation_points = T[v for (v, b) in enumerate(is_articulation_pt) if b] | ||
|
|
||
| return articulation_points | ||
| end | ||
|
|
||
| """ | ||
| is_articulation(g, v) | ||
|
|
||
| Determine whether `v` is an | ||
| [articulation point](https://en.wikipedia.org/wiki/Biconnected_component) of an undirected | ||
| graph `g`, returning `true` if so and `false` otherwise. | ||
|
|
||
| See also [`articulation`](@ref). | ||
|
|
||
| # Examples | ||
| ```jldoctest | ||
| julia> using Graphs | ||
|
|
||
| julia> g = path_graph(5) | ||
| {5, 4} undirected simple Int64 graph | ||
|
|
||
| julia> articulation(g) | ||
| 3-element Vector{Int64}: | ||
| 2 | ||
| 3 | ||
| 4 | ||
|
|
||
| julia> is_articulation(g, 2) | ||
| true | ||
|
|
||
| julia> is_articulation(g, 1) | ||
| false | ||
| ``` | ||
| """ | ||
| function is_articulation end | ||
| @traitfn function is_articulation(g::AG::(!IsDirected), v::T) where {T,AG<:AbstractGraph{T}} | ||
| s = Vector{Tuple{T,T,T}}() | ||
| low = zeros(T, nv(g)) | ||
| pre = zeros(T, nv(g)) | ||
|
|
||
| return articulation_dfs!(nothing, g, v, s, low, pre) | ||
| end | ||
|
|
||
| @traitfn function articulation_dfs!( | ||
| is_articulation_pt::Union{Nothing,BitVector}, | ||
| g::AG::(!IsDirected), | ||
| u::T, | ||
| s::Vector{Tuple{T,T,T}}, | ||
| low::Vector{T}, | ||
| pre::Vector{T}, | ||
gdalle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ) where {T,AG<:AbstractGraph{T}} | ||
| if !isnothing(is_articulation_pt) | ||
| if pre[u] != 0 | ||
| return is_articulation_pt | ||
| end | ||
| end | ||
|
Comment on lines
+85
to
+89
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain this shortcut in a comment? |
||
|
|
||
| v = u | ||
| children = 0 | ||
| wi::T = zero(T) | ||
| w::T = zero(T) | ||
| cnt::T = one(T) | ||
| first_time = true | ||
|
|
||
| # TODO the algorithm currently relies on the assumption that | ||
| # outneighbors(g, v) is indexable. This assumption might not be true | ||
| # in general, so in case that outneighbors does not produce a vector | ||
| # we collect these vertices. This might lead to a large number of | ||
| # allocations, so we should find a way to handle that case differently, | ||
| # or require inneighbors, outneighbors and neighbors to always | ||
| # return indexable collections. | ||
|
|
||
| while !isempty(s) || first_time | ||
| first_time = false | ||
| if wi < 1 | ||
| pre[v] = cnt | ||
| cnt += 1 | ||
| low[v] = pre[v] | ||
| v_neighbors = collect_if_not_vector(outneighbors(g, v)) | ||
| wi = 1 | ||
| else | ||
| wi, u, v = pop!(s) | ||
| v_neighbors = collect_if_not_vector(outneighbors(g, v)) | ||
| w = v_neighbors[wi] | ||
| low[v] = min(low[v], low[w]) | ||
| if low[w] >= pre[v] && u != v | ||
| if isnothing(is_articulation_pt) | ||
| if v == u | ||
| return true | ||
| end | ||
| else | ||
| is_articulation_pt[v] = true | ||
| end | ||
| wi += 1 | ||
| end | ||
| while wi <= length(v_neighbors) | ||
| w = v_neighbors[wi] | ||
| if pre[w] == 0 | ||
| if u == v | ||
| children += 1 | ||
| end | ||
| push!(s, (wi, u, v)) | ||
| wi = 0 | ||
| u = v | ||
| v = w | ||
| break | ||
| elseif w != u | ||
| low[v] = min(low[v], pre[w]) | ||
| wi += 1 | ||
| end | ||
| while wi <= length(v_neighbors) | ||
| w = v_neighbors[wi] | ||
| if pre[w] == 0 | ||
| if u == v | ||
| children += 1 | ||
| end | ||
| wi += 1 | ||
| push!(s, (wi, u, v)) | ||
| wi = 0 | ||
| u = v | ||
| v = w | ||
| break | ||
| elseif w != u | ||
| low[v] = min(low[v], pre[w]) | ||
| end | ||
| wi < 1 && continue | ||
| wi += 1 | ||
| end | ||
| wi < 1 && continue | ||
| end | ||
|
|
||
| if children > 1 | ||
| if children > 1 | ||
| if isnothing(is_articulation_pt) | ||
| return u == v | ||
| else | ||
| is_articulation_pt[u] = true | ||
| end | ||
| end | ||
|
|
||
| articulation_points = Vector{T}() | ||
|
|
||
| for u in findall(is_articulation_pt) | ||
| push!(articulation_points, T(u)) | ||
| end | ||
|
|
||
| return articulation_points | ||
| return isnothing(is_articulation_pt) ? false : is_articulation_pt | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the idea is to call
is_articulationseveral times in a row for different vertices, these allocations might be reusable? Maybe we should exposearticulation_dfs!?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that'd be alright with me also. The
_dfspart is an implementation detail though, so maybe this should just be calledis_articulation!, taking work-arrays forpreandlowthen? Let me know what you think and I'll change to that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, but then we should at the very least explain how to initialize these arguments (and ideally what they mean)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also discussion in #407 (comment): maybe we should just be calling these
work_buffer1andwork_buffer2(adding some comments to explain in the code - or reassigning them to more aptly named variables) to avoid exposing unnecessary complications and implementation details to users.