graph_matching 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|