graph_matching 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +112 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +205 -0
- data/Rakefile +9 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +33 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/compare.gnuplot +19 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/edges_times_vertexes.data +500 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/plot.gnuplot +21 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/plot.png +0 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/time.data +499 -0
- data/benchmark/mcm_general/complete_graphs/benchmark.rb +30 -0
- data/benchmark/mcm_general/complete_graphs/plot.gnuplot +19 -0
- data/benchmark/mcm_general/complete_graphs/plot.png +0 -0
- data/benchmark/mcm_general/complete_graphs/time.data +499 -0
- data/benchmark/mcm_general/complete_graphs/v_cubed.data +500 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +43 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/nmN.data +499 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/nmN.xlsx +0 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/plot.gnuplot +22 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/plot.png +0 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/time.data +299 -0
- data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +29 -0
- data/benchmark/mwm_general/complete_graphs/benchmark.rb +32 -0
- data/benchmark/mwm_general/complete_graphs/compare.gnuplot +19 -0
- data/benchmark/mwm_general/complete_graphs/mn_log_n.data +299 -0
- data/benchmark/mwm_general/complete_graphs/mn_log_n.xlsx +0 -0
- data/benchmark/mwm_general/complete_graphs/plot.gnuplot +22 -0
- data/benchmark/mwm_general/complete_graphs/plot.png +0 -0
- data/benchmark/mwm_general/complete_graphs/time.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +39 -0
- data/benchmark/mwm_general/incomplete_graphs/plot.gnuplot +22 -0
- data/benchmark/mwm_general/incomplete_graphs/plot.png +0 -0
- data/benchmark/mwm_general/incomplete_graphs/time_10_pct.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/time_20_pct.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/time_30_pct.data +299 -0
- data/graph_matching.gemspec +35 -0
- data/lib/graph_matching.rb +15 -0
- data/lib/graph_matching/algorithm/matching_algorithm.rb +23 -0
- data/lib/graph_matching/algorithm/mcm_bipartite.rb +118 -0
- data/lib/graph_matching/algorithm/mcm_general.rb +289 -0
- data/lib/graph_matching/algorithm/mwm_bipartite.rb +147 -0
- data/lib/graph_matching/algorithm/mwm_general.rb +1086 -0
- data/lib/graph_matching/algorithm/mwmg_delta_assertions.rb +94 -0
- data/lib/graph_matching/assertion.rb +41 -0
- data/lib/graph_matching/core_ext/set.rb +36 -0
- data/lib/graph_matching/directed_edge_set.rb +31 -0
- data/lib/graph_matching/errors.rb +23 -0
- data/lib/graph_matching/graph/bigraph.rb +37 -0
- data/lib/graph_matching/graph/graph.rb +63 -0
- data/lib/graph_matching/graph/weighted.rb +112 -0
- data/lib/graph_matching/graph/weighted_bigraph.rb +17 -0
- data/lib/graph_matching/graph/weighted_graph.rb +17 -0
- data/lib/graph_matching/integer_vertexes.rb +29 -0
- data/lib/graph_matching/matching.rb +120 -0
- data/lib/graph_matching/ordered_set.rb +59 -0
- data/lib/graph_matching/version.rb +6 -0
- data/lib/graph_matching/visualize.rb +93 -0
- data/profile/mcm_bipartite/compare.sh +15 -0
- data/profile/mcm_bipartite/publish.sh +12 -0
- data/profile/mwm_general/compare.sh +15 -0
- data/profile/mwm_general/profile.rb +28 -0
- data/profile/mwm_general/publish.sh +12 -0
- data/research/1965_edmonds.pdf +0 -0
- data/research/1975_even_kariv.pdf +0 -0
- data/research/1976_gabow.pdf +0 -0
- data/research/1980_micali_vazirani.pdf +0 -0
- data/research/1985_gabow.pdf +0 -0
- data/research/2002_tarjan.pdf +0 -0
- data/research/2013_zwick.pdf +0 -0
- data/research/examples/unweighted_general/1.txt +86 -0
- data/research/goodwin.pdf +0 -0
- data/research/kavathekar-scribe.pdf +0 -0
- data/research/kusner.pdf +0 -0
- data/research/van_rantwijk/mwm_example.py +19 -0
- data/research/van_rantwijk/mwmatching.py +945 -0
- data/spec/graph_matching/algorithm/matching_algorithm_spec.rb +14 -0
- data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +98 -0
- data/spec/graph_matching/algorithm/mcm_general_spec.rb +159 -0
- data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +82 -0
- data/spec/graph_matching/algorithm/mwm_general_spec.rb +439 -0
- data/spec/graph_matching/graph/bigraph_spec.rb +73 -0
- data/spec/graph_matching/graph/graph_spec.rb +53 -0
- data/spec/graph_matching/graph/weighted_spec.rb +29 -0
- data/spec/graph_matching/integer_vertexes_spec.rb +21 -0
- data/spec/graph_matching/matching_spec.rb +89 -0
- data/spec/graph_matching/visualize_spec.rb +38 -0
- data/spec/graph_matching_spec.rb +9 -0
- data/spec/spec_helper.rb +26 -0
- metadata +263 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'graph_matching/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'graph_matching'
|
|
8
|
+
spec.version = GraphMatching::VERSION
|
|
9
|
+
spec.authors = ['Jared Beck']
|
|
10
|
+
spec.email = ['jared@jaredbeck.com']
|
|
11
|
+
spec.summary = 'Finds maximum matchings in undirected graphs.'
|
|
12
|
+
spec.description = <<-EOS
|
|
13
|
+
Efficient algorithms for maximum cardinality
|
|
14
|
+
and weighted matchings in undirected graphs.
|
|
15
|
+
EOS
|
|
16
|
+
spec.homepage = 'https://github.com/jaredbeck/graph_matching'
|
|
17
|
+
spec.license = 'MIT'
|
|
18
|
+
|
|
19
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
20
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
21
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
22
|
+
spec.require_paths = ['lib']
|
|
23
|
+
|
|
24
|
+
spec.required_ruby_version = '>= 1.9.3'
|
|
25
|
+
|
|
26
|
+
spec.add_runtime_dependency 'rgl', '~> 0.5.0'
|
|
27
|
+
|
|
28
|
+
spec.add_development_dependency 'bundler'
|
|
29
|
+
spec.add_development_dependency 'pry'
|
|
30
|
+
spec.add_development_dependency 'pry-nav'
|
|
31
|
+
spec.add_development_dependency 'rspec-core', '~> 3.2'
|
|
32
|
+
spec.add_development_dependency 'rspec-expectations', '~> 3.2'
|
|
33
|
+
spec.add_development_dependency 'rspec-mocks', '~> 3.2'
|
|
34
|
+
spec.add_development_dependency 'rubocop'
|
|
35
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'graph_matching/core_ext/set'
|
|
4
|
+
require 'graph_matching/graph/graph'
|
|
5
|
+
require 'graph_matching/graph/bigraph'
|
|
6
|
+
require 'graph_matching/graph/weighted_graph'
|
|
7
|
+
require 'graph_matching/graph/weighted_bigraph'
|
|
8
|
+
require 'graph_matching/errors'
|
|
9
|
+
require 'graph_matching/version'
|
|
10
|
+
require 'graph_matching/visualize'
|
|
11
|
+
|
|
12
|
+
# Efficient algorithms for maximum cardinality and weighted
|
|
13
|
+
# matchings in undirected graphs.
|
|
14
|
+
module GraphMatching
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require_relative '../assertion'
|
|
4
|
+
|
|
5
|
+
module GraphMatching
|
|
6
|
+
module Algorithm
|
|
7
|
+
|
|
8
|
+
# All matching algorithms operate on a graph, hence the
|
|
9
|
+
# common constructor.
|
|
10
|
+
class MatchingAlgorithm
|
|
11
|
+
attr_reader :g
|
|
12
|
+
|
|
13
|
+
def initialize(graph)
|
|
14
|
+
@g = graph
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def assert(obj)
|
|
18
|
+
Assertion.new(obj)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require_relative '../matching'
|
|
4
|
+
require_relative 'matching_algorithm'
|
|
5
|
+
|
|
6
|
+
module GraphMatching
|
|
7
|
+
module Algorithm
|
|
8
|
+
|
|
9
|
+
# `MCMBipartite` implements Maximum Cardinality Matching in
|
|
10
|
+
# bipartite graphs.
|
|
11
|
+
class MCMBipartite < MatchingAlgorithm
|
|
12
|
+
|
|
13
|
+
def initialize(graph)
|
|
14
|
+
assert(graph).is_a(Graph::Bigraph)
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def match
|
|
19
|
+
u, v = g.partition
|
|
20
|
+
m = []
|
|
21
|
+
|
|
22
|
+
while true
|
|
23
|
+
|
|
24
|
+
# Begin each stage by clearing all labels and marks
|
|
25
|
+
t = []
|
|
26
|
+
predecessors = {}
|
|
27
|
+
aug_path = nil
|
|
28
|
+
|
|
29
|
+
# Label unmatched vertexes in U with label R. These R-vertexes
|
|
30
|
+
# are candidates for the start of an augmenting path.
|
|
31
|
+
unmarked = r = u.select { |i| m[i] == nil }
|
|
32
|
+
|
|
33
|
+
# While there are unmarked R-vertexes
|
|
34
|
+
while aug_path.nil? && start = unmarked.pop
|
|
35
|
+
|
|
36
|
+
# Follow the unmatched edges (if any) to vertexes in V
|
|
37
|
+
# ignoring any V-vertexes already labeled T
|
|
38
|
+
unlabeled_across_unmatched_edges_from(start, g, m ,t).each do |vi|
|
|
39
|
+
t << vi
|
|
40
|
+
predecessors[vi] = start
|
|
41
|
+
|
|
42
|
+
# If there are matched edges, follow each to a vertex
|
|
43
|
+
# in U and label the U-vertex with R. Otherwise,
|
|
44
|
+
# backtrack to construct an augmenting path.
|
|
45
|
+
adj_u_in_m = matched_adjacent(vi, start, g, m)
|
|
46
|
+
|
|
47
|
+
adj_u_in_m.each do |ui|
|
|
48
|
+
r << ui
|
|
49
|
+
predecessors[ui] = vi
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if adj_u_in_m.empty?
|
|
53
|
+
aug_path = backtrack_from(vi, predecessors)
|
|
54
|
+
break
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if aug_path.nil?
|
|
60
|
+
break
|
|
61
|
+
else
|
|
62
|
+
m = augment(m, aug_path)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Matching.gabow(m)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def assert_valid_aug_path(path)
|
|
72
|
+
unless path.length >= 2 && path.length.even?
|
|
73
|
+
raise "Invalid augmenting path"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def augment(m, path)
|
|
78
|
+
assert_valid_aug_path(path)
|
|
79
|
+
ix = 0
|
|
80
|
+
while ix < path.length
|
|
81
|
+
i = path[ix]
|
|
82
|
+
j = path[ix + 1]
|
|
83
|
+
mi = m[i]
|
|
84
|
+
mj = m[j]
|
|
85
|
+
m[mi] = nil unless mi == nil
|
|
86
|
+
m[mj] = nil unless mj == nil
|
|
87
|
+
m[i] = j
|
|
88
|
+
m[j] = i
|
|
89
|
+
ix += 2
|
|
90
|
+
end
|
|
91
|
+
m
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def backtrack_from(end_vertex, predecessors)
|
|
95
|
+
augmenting_path = [end_vertex]
|
|
96
|
+
while predecessors.has_key?(augmenting_path.last)
|
|
97
|
+
augmenting_path.push(predecessors[augmenting_path.last])
|
|
98
|
+
end
|
|
99
|
+
augmenting_path
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def matched_adjacent(from, except, g, m)
|
|
103
|
+
g.adjacent_vertices(from).select { |i|
|
|
104
|
+
i != except && m[from] == i
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Look across unmatched edges from `v` to find vertexes
|
|
109
|
+
# not labeled `t`.
|
|
110
|
+
def unlabeled_across_unmatched_edges_from(v, g, m, t)
|
|
111
|
+
g.adjacent_vertices(v).select { |i|
|
|
112
|
+
m[v] != i && !t.include?(i)
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require_relative '../directed_edge_set'
|
|
4
|
+
require_relative '../matching'
|
|
5
|
+
require_relative 'matching_algorithm'
|
|
6
|
+
|
|
7
|
+
module GraphMatching
|
|
8
|
+
module Algorithm
|
|
9
|
+
|
|
10
|
+
# `MCMGeneral` implements Maximum Cardinality Matching in
|
|
11
|
+
# general graphs (as opposed to bipartite).
|
|
12
|
+
class MCMGeneral < MatchingAlgorithm
|
|
13
|
+
|
|
14
|
+
# An LFlag represents a flag on an edge during Gabow's `l` function.
|
|
15
|
+
class LFlag
|
|
16
|
+
attr_reader :edge
|
|
17
|
+
def initialize(edge)
|
|
18
|
+
@edge = edge
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(graph)
|
|
23
|
+
assert(graph).is_a(Graph::Graph)
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def match
|
|
28
|
+
return Matching.new if g.empty?
|
|
29
|
+
raise DisconnectedGraph unless g.connected?
|
|
30
|
+
e(g)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# `e` constructs a maximum matching on a graph. It starts a
|
|
36
|
+
# search for an augmenting path to each unmatched vertex u.
|
|
37
|
+
# It scans edges of the graph, deciding to assign new labels
|
|
38
|
+
# or to augment the matching.
|
|
39
|
+
def e(g)
|
|
40
|
+
first = []
|
|
41
|
+
label = []
|
|
42
|
+
mate = []
|
|
43
|
+
|
|
44
|
+
# E0. [Initialize.] Read the graph into adjacency lists,
|
|
45
|
+
# numbering the vertices 1 to V and the edges V + 1 to
|
|
46
|
+
# V + 2W. Create a dummy vertex 0 For 0 <= i <= V, set
|
|
47
|
+
# LABEL(u) <- -1, MATE(i) <- 0 (all vertices are nonouter
|
|
48
|
+
# and unmatched) Set u <- 0
|
|
49
|
+
|
|
50
|
+
label.fill(-1, 0, g.size + 1)
|
|
51
|
+
mate.fill(0, 0, g.size + 1)
|
|
52
|
+
u = 0
|
|
53
|
+
|
|
54
|
+
# El. [Find unmatched vertex ] Set u = u + 1. If u > V,
|
|
55
|
+
# halt; MATE contains a maximum matching Otherwise, if vertex
|
|
56
|
+
# u is matched, repeat step E1 Otherwise (u is unmatched, so
|
|
57
|
+
# assign a start label and begin a new search)
|
|
58
|
+
# set LABEL(u) = FIRST(u) = 0
|
|
59
|
+
|
|
60
|
+
while true do
|
|
61
|
+
u += 1
|
|
62
|
+
break if u > g.size
|
|
63
|
+
if mate[u] != 0
|
|
64
|
+
next # repeat E1
|
|
65
|
+
else
|
|
66
|
+
label[u] = first[u] = 0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# E2 [Choose an edge ] Choose an edge xy, where x is an outer
|
|
70
|
+
# vertex. (An edge vw may be chosen twice in a search--once
|
|
71
|
+
# with x = v, and once with x = w.) If no such edge exists,
|
|
72
|
+
# go to E7. (Edges xy can be chosen in an arbitrary order. A
|
|
73
|
+
# possible choice method is "breadth-first": an outer vertex
|
|
74
|
+
# x = x1 is chosen, and edges (x1,y) are chosen in succeeding
|
|
75
|
+
# executions of E2, when all such edges have been chosen, the
|
|
76
|
+
# vertex x2 that was labeled immediately after x1 is chosen,
|
|
77
|
+
# and the process is repeated for x = x2. This breadth-first
|
|
78
|
+
# method requires that Algorithm E maintain a list of outer
|
|
79
|
+
# vertices, x1, x2, ...)
|
|
80
|
+
|
|
81
|
+
searching = true
|
|
82
|
+
visited_nodes = Set.new
|
|
83
|
+
visited_edges = DirectedEdgeSet.new(g.size)
|
|
84
|
+
q = OrderedSet[u]
|
|
85
|
+
while searching && !q.empty?
|
|
86
|
+
x = q.deq
|
|
87
|
+
visited_nodes.add(x)
|
|
88
|
+
adjacent = g.adjacent_vertex_set(x)
|
|
89
|
+
discovered = adjacent - visited_edges.adjacent_vertices(x)
|
|
90
|
+
|
|
91
|
+
discovered.each do |y|
|
|
92
|
+
visited_edges.add(x, y)
|
|
93
|
+
|
|
94
|
+
# E3. [Augment the matching.] If y is unmatched and y != u,
|
|
95
|
+
# set MATE(y) = x, call R(x, y): then go to E7 (R
|
|
96
|
+
# completes the augment along path (y)*P(x))
|
|
97
|
+
|
|
98
|
+
if mate[y] == 0 && y != u
|
|
99
|
+
mate[y] = x
|
|
100
|
+
r(x, y, label, mate)
|
|
101
|
+
searching = false # go to E7
|
|
102
|
+
break
|
|
103
|
+
|
|
104
|
+
# E4. [Assign edge labels.] If y is outer, call L, then go to
|
|
105
|
+
# E2 (L assigns edge label n(xy) to nonouter vertices in P(x)
|
|
106
|
+
# and P(y))
|
|
107
|
+
|
|
108
|
+
elsif outer?(label[y])
|
|
109
|
+
l(x, y, first, label, mate, q, visited_nodes)
|
|
110
|
+
|
|
111
|
+
# E5. [Assign a vertex label.] Set v <- MATE(y). If v is
|
|
112
|
+
# nonouter, set LABEL(v) <- x, FIRST(v) <- y, and go to E2
|
|
113
|
+
#
|
|
114
|
+
# E6. [Get next edge.] Go to E2 (y is nonouter and MATE(y) is
|
|
115
|
+
# outer, so edge xy adds nothing).
|
|
116
|
+
|
|
117
|
+
else
|
|
118
|
+
v = mate[y]
|
|
119
|
+
if label[v] == -1 # nonouter
|
|
120
|
+
label[v] = x
|
|
121
|
+
first[v] = y
|
|
122
|
+
end
|
|
123
|
+
unless visited_nodes.include?(v)
|
|
124
|
+
q.enq(v)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
#
|
|
132
|
+
# E7. [Stop the search] Set LABEL(O) <- -1. For all outer
|
|
133
|
+
# vertices i set LABEL(i) <- LABEL(MATE(i)) <- -1 Then go
|
|
134
|
+
# to E1 (now all vertexes are nonouter for the next search).
|
|
135
|
+
#
|
|
136
|
+
|
|
137
|
+
label[0] = -1
|
|
138
|
+
label.each_with_index do |obj, ix|
|
|
139
|
+
if ix > 0 && outer?(obj)
|
|
140
|
+
label[ix] = label[mate[ix]] = -1
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
end # while e0_loop
|
|
145
|
+
|
|
146
|
+
Matching.gabow(mate)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def edge_label?(label_value)
|
|
150
|
+
label_value.is_a?(RGL::Edge::UnDirectedEdge)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# L assigns the edge label n(xy) to nonouter vertices. Edge xy
|
|
154
|
+
# joins outer vertices x, y. L sets join to the first nonouter
|
|
155
|
+
# vertex in both P(x) and P(y). Then it labels all nonouter
|
|
156
|
+
# vertices preceding join in P(x) or P(y).
|
|
157
|
+
def l(x, y, first, label, mate, q, visited_nodes)
|
|
158
|
+
|
|
159
|
+
# L0. [Initialize.] Set r <- FIRST(x), s <= FIRST(y).
|
|
160
|
+
# If r = s, return (no vertices can be labeled).
|
|
161
|
+
# Otherwise flag r and s. (Steps L1-L2 find join by advancing
|
|
162
|
+
# alternately along paths P(x) and P(y). Flags are assigned
|
|
163
|
+
# to nonouter vertices r in these paths. This is done by
|
|
164
|
+
# setting LABEL(r) to a negative edge number, LABEL(r) <- -n(xy).
|
|
165
|
+
# This way, each invocation of L uses a distinct flag value.)
|
|
166
|
+
|
|
167
|
+
r = first[x]
|
|
168
|
+
s = first[y]
|
|
169
|
+
|
|
170
|
+
if r == s
|
|
171
|
+
return # no vertices can be labeled
|
|
172
|
+
else
|
|
173
|
+
label[r] = LFlag.new(n(x, y))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# L1. [Switch paths ] If s != 0, interchange r and s, r <-> s
|
|
177
|
+
# (r is a flagged nonouter vertex, alternately in P(x) and P(y)).
|
|
178
|
+
|
|
179
|
+
finding_join = true
|
|
180
|
+
while finding_join
|
|
181
|
+
if s != 0
|
|
182
|
+
temp = r
|
|
183
|
+
r = s
|
|
184
|
+
s = temp
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# L2. [Next nonouter vertex.] Set r <- FIRST(LABEL(MATE(r)))
|
|
188
|
+
# (r is set to the next nonouter vertex in P(x) or P(y)). If
|
|
189
|
+
# r is not flagged, flag r and go to L1 Otherwise set
|
|
190
|
+
# join <- r and go to L3.
|
|
191
|
+
|
|
192
|
+
r = first[label[mate[r]]]
|
|
193
|
+
if label[r].is_a?(LFlag)
|
|
194
|
+
join = r
|
|
195
|
+
finding_join = false
|
|
196
|
+
else
|
|
197
|
+
label[r] = LFlag.new(n(x, y))
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# L3. [Label vertices in P(x), P(y).] (All nonouter vertexes
|
|
202
|
+
# between x and join, or y and join, will be assigned edge
|
|
203
|
+
# labels. See Figure 4(a).) Set v <- FIRST(x) and do L4. Then
|
|
204
|
+
# set v <- FIRST(y) and do L4. Then go to L5.
|
|
205
|
+
|
|
206
|
+
[first[x], first[y]].each do |v|
|
|
207
|
+
|
|
208
|
+
# L4 [Label v] If v != join, set LABEL(v) <- n(xy), FIRST(v) <- join,
|
|
209
|
+
# v <- FIRST(LABEL(MATE(v))) and repeat step L4
|
|
210
|
+
# Otherwise continue as specified in L3.
|
|
211
|
+
|
|
212
|
+
until v == join
|
|
213
|
+
label[v] = n(x, y)
|
|
214
|
+
unless visited_nodes.include?(v)
|
|
215
|
+
q.enq(v)
|
|
216
|
+
end
|
|
217
|
+
first[v] = join
|
|
218
|
+
v = first[label[mate[v]]]
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# L5 [Update FIRST] For each outer vertex i, if FIRST(i) is
|
|
223
|
+
# outer, set FIRST(i) <- join. (Join is now the first nonouter
|
|
224
|
+
# vertex in P(i))
|
|
225
|
+
|
|
226
|
+
label.each_with_index do |l, i|
|
|
227
|
+
if i > 0 && outer?(l) && outer?(label[first[i]])
|
|
228
|
+
first[i] = join
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# L6. [Done] Return
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Gabow (1976) describes a function `n` which returns the number
|
|
236
|
+
# of the edge from `x` to `y`. Because we are using RGL, and
|
|
237
|
+
# not implementing our own adjacency lists, we can simply return
|
|
238
|
+
# an RGL::Edge::UnDirectedEdge.
|
|
239
|
+
def n(x, y)
|
|
240
|
+
RGL::Edge::UnDirectedEdge.new(x, y)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def outer?(label_value)
|
|
244
|
+
!label_value.is_a?(Integer) || label_value >= 0
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# R(v, w) rematches edges in the augmenting path. Vertex v is
|
|
248
|
+
# outer. Part of path (w) * P(v) is in the augmenting path. It
|
|
249
|
+
# gets re-matched by R(v, w) (Although R sets MATE(v) <- w, it
|
|
250
|
+
# does not set MATE(w) <- v. This is done in step E3 or another
|
|
251
|
+
# call to R.) R is a recursive routine.
|
|
252
|
+
def r(v, w, label, mate)
|
|
253
|
+
|
|
254
|
+
# R1. [Match v to w ] Set t <- MATE(v), MATE(v) <- w.
|
|
255
|
+
# If MATE(t) != v, return (the path is completely re-matched)
|
|
256
|
+
|
|
257
|
+
t = mate[v]
|
|
258
|
+
mate[v] = w
|
|
259
|
+
return if mate[t] != v
|
|
260
|
+
|
|
261
|
+
# R2. [Rematch a path.] If v has a vertex label, set
|
|
262
|
+
# MATE(t) <- LABEL(v), call R(LABEL(v), t) recursively, and
|
|
263
|
+
# then return.
|
|
264
|
+
|
|
265
|
+
if vertex_label?(label[v])
|
|
266
|
+
mate[t] = label[v]
|
|
267
|
+
r(label[v], t, label, mate)
|
|
268
|
+
|
|
269
|
+
# R3. [Rematch two paths.] (Vertex v has an edge label)
|
|
270
|
+
# Set x, y to vertices so LABEL(v) = n(xy), call R(x, y)
|
|
271
|
+
# recursively, call R(y, x) recursively, and then return.
|
|
272
|
+
|
|
273
|
+
elsif edge_label?(label[v])
|
|
274
|
+
x, y = label[v].to_a
|
|
275
|
+
r(x, y, label, mate)
|
|
276
|
+
r(y, x, label, mate)
|
|
277
|
+
else
|
|
278
|
+
fail "Vertex #{v} has an unexpected label type"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def vertex_label?(label_value)
|
|
283
|
+
label_value.is_a?(Integer) && label_value > 0
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
end
|
|
289
|
+
end
|