-
Notifications
You must be signed in to change notification settings - Fork 6
[WIP] Feature cardinality matching #18
base: master
Are you sure you want to change the base?
Changes from 3 commits
bc29035
a0e4302
2c61065
5cec648
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| """ | ||
| maximum_cardinality_matching(g::Graph) | ||
|
|
||
| Given a graph `g` returns a maximum cardinality matching. | ||
| This is the same as running `maximum_weight_matching` with default weights (`1`) | ||
| but is faster without needing a JuMP solver. | ||
|
|
||
| Returns MatchingResult containing: | ||
| - the maximum cardinality that can be achieved | ||
| - a list of each vertex's match (or -1 for unmatched vertices) | ||
| """ | ||
| function maximum_cardinality_matching(g::AbstractGraph{U}) where U<:Integer | ||
Wikunia marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| n = nv(g) | ||
| matching = -ones(Int, n) | ||
Wikunia marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # the number of edges that can possibly be part of a matching | ||
| max_generally_possible = fld(n, 2) | ||
|
|
||
| # get initial matching | ||
| matching_len = 0 | ||
| for e in edges(g) | ||
| # if unmatched | ||
| if matching[e.src] == -1 && matching[e.dst] == -1 | ||
| matching[e.src] = e.dst | ||
| matching[e.dst] = e.src | ||
| matching_len += 1 | ||
| end | ||
| end | ||
|
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. Maybe it would be a good idea, to have this as a separate function? Then it could also be used for finding a greed maximal cardinality matching and be used by other functions. We could pass it in as an optional argument or a keyword argument. Furthermore, I was thinking, maybe it would be more efficient to loop over the vertices and then over all neighbors of that vertex? I.e something like for u in vertices(g)
matching[u] == -1 || continue
for v in neighbors[u]
u <= v && continue
matching[u] = v
matching[v] = u
matching_len += 1
end
endThis approach only works, if we assume that Also, be careful with self-loops, these should never be in a matching. 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. If neighbors is implemented in a fast manner yes. Thought that looping over the edges might be faster than accessing the neighbors but yours has an earlier break. Maybe I can test it on a larger graph. Good point with self loops! 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. I think for 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. Is now a separate function but still uses |
||
|
|
||
| # if there are at least two free vertices | ||
| if matching_len < max_generally_possible | ||
Wikunia marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| parents = zeros(Int, n) | ||
| visited = falses(n) | ||
|
|
||
| @inbounds while matching_len < max_generally_possible | ||
| cur_level = Vector{Int}() | ||
| sizehint!(cur_level, n) | ||
| next_level = Vector{Int}() | ||
| sizehint!(next_level, n) | ||
Wikunia marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # get starting free vertex | ||
| free_vertex = 0 | ||
| found = false | ||
| for v in vertices(g) | ||
| if matching[v] == -1 | ||
| visited[v] = true | ||
| free_vertex = v | ||
| push!(cur_level, v) | ||
|
|
||
| level = 1 | ||
| found = false | ||
| # find augmenting path | ||
| while !isempty(cur_level) | ||
| for v in cur_level | ||
|
|
||
| if level % 2 == 1 | ||
| for t in outneighbors(g, v) | ||
| # found an augmenting path if connected to a free vertex | ||
| if matching[t] == -1 | ||
| # traverse the augmenting path backwards and change the matching | ||
| current_src = t | ||
| current_dst = v | ||
| back_level = 1 | ||
| while current_dst != 0 | ||
| # add every second edge to the matching (this also overwrites the current matching) | ||
| if back_level % 2 == 1 | ||
Wikunia marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| matching[current_src] = current_dst | ||
| matching[current_dst] = current_src | ||
| end | ||
| current_src = current_dst | ||
| current_dst = parents[current_dst] | ||
| back_level += 1 | ||
| end | ||
| # added exactly one edge to the matching | ||
| matching_len += 1 | ||
| # terminate current search | ||
| found = true | ||
| break | ||
| end | ||
|
|
||
| # use a non matching edge | ||
| if !visited[t] && matching[v] != t | ||
| visited[t] = true | ||
| parents[t] = v | ||
| push!(next_level, t) | ||
| end | ||
| end | ||
| found && break | ||
| else # use a matching edge | ||
| t = matching[v] | ||
| if !visited[t] | ||
| visited[t] = true | ||
| parents[t] = v | ||
| push!(next_level, t) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| empty!(cur_level) | ||
| cur_level, next_level = next_level, cur_level | ||
|
|
||
| level += 1 | ||
| found && break | ||
| end # end finding augmenting path | ||
| found && break | ||
| parents .= 0 | ||
| visited .= false | ||
| end | ||
| end | ||
| # checked all free vertices: | ||
| # no augmenting path found => no better matching | ||
| !found && break | ||
| end | ||
| end | ||
|
|
||
| return MatchingResult(matching_len, matching) | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.