From a1ea44eb618bda03861f77c9b5285f41a2a2367d Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 25 Jan 2024 19:03:36 +0100 Subject: [PATCH] fix mincut() (#325) * s-t cut weight should be adj_cost. fixes #324 * mincut(): simplify cutweight computation * improve performance by 20x using dequeue_pair! * _merge_vertex!: use inplace updates saves another 20% runtime * set u to new root in heuristic merge * add comments --------- Co-authored-by: Etienne dg --- src/traversals/maxadjvisit.jl | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/traversals/maxadjvisit.jl b/src/traversals/maxadjvisit.jl index 0c6a33de3..7ebcd36f2 100644 --- a/src/traversals/maxadjvisit.jl +++ b/src/traversals/maxadjvisit.jl @@ -26,6 +26,9 @@ assumed to be 1. # in which case we'll return immediately. (nvg > one(U)) || return (Vector{Int8}([1]), zero(T)) + # to avoid reallocating lists in fadjlist, we have some already merged vertices + # still appearing in fadjlist. When iterating neighbors, is_merged makes sure we + # don't consider them is_merged = falses(nvg) merged_vertices = IntDisjointSets(U(nvg)) graph_size = nvg @@ -54,7 +57,6 @@ assumed to be 1. is_processed = falses(nvg) @inbounds while graph_size > 1 - cutweight = zero(T) is_processed .= false is_processed[u] = true # initialize pq @@ -66,32 +68,23 @@ assumed to be 1. for v in fadjlist[u] (is_merged[v] || v == u) && continue pq[v] = w[u, v] - cutweight += w[u, v] end # Minimum cut phase + local cutweight while true last_vertex = u - u, adj_cost = first(pq) - dequeue!(pq) + u, cutweight = dequeue_pair!(pq) isempty(pq) && break for v in fadjlist[u] - (is_merged[v] || u == v) && continue - # if the target of e is already marked then decrease cutweight - # otherwise, increase it - ew = w[u, v] - if is_processed[v] - cutweight -= ew - else - cutweight += ew - pq[v] += ew - end + (is_processed[v] || is_merged[v] || u == v) && continue + pq[v] += w[u, v] end is_processed[u] = true - # adj_cost is a lower bound on the cut separating the two last vertices - # encountered, so if adj_cost >= bestweight, we can already merge these + # cutweight is a lower bound on the cut separating the two last vertices + # encountered, so if cutweight >= bestweight, we can already merge these # vertices to save one phase. - if adj_cost >= bestweight - _merge_vertex!(merged_vertices, fadjlist, is_merged, w, u, last_vertex) + if cutweight >= bestweight + u = _merge_vertex!(merged_vertices, fadjlist, is_merged, w, u, last_vertex) graph_size -= 1 end end @@ -105,14 +98,14 @@ assumed to be 1. end # merge u and last_vertex - root = _merge_vertex!(merged_vertices, fadjlist, is_merged, w, u, last_vertex) + u = _merge_vertex!(merged_vertices, fadjlist, is_merged, w, u, last_vertex) graph_size -= 1 - u = root # we are sure this vertex was not merged, so the next phase start from it end return (convert(Vector{Int8}, parities) .+ one(Int8), bestweight) end function _merge_vertex!(merged_vertices, fadjlist, is_merged, w, u, v) + # root is kept, non-root is discarded root = union!(merged_vertices, u, v) non_root = (root == u) ? v : u is_merged[non_root] = true @@ -122,7 +115,7 @@ function _merge_vertex!(merged_vertices, fadjlist, is_merged, w, u, v) w[v2, root] = w[root, v2] end # update neighbors - fadjlist[root] = union(fadjlist[root], fadjlist[non_root]) + union!(fadjlist[root], fadjlist[non_root]) for v in fadjlist[non_root] if root ∉ fadjlist[v] push!(fadjlist[v], root)