rgl 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +19 -10
- data/Gemfile +3 -0
- data/{README → README.rdoc} +70 -98
- data/Rakefile +44 -150
- data/examples/canvas.rb +63 -64
- data/examples/examples.rb +42 -42
- data/examples/graph.dot +46 -0
- data/examples/images/example.jpg +0 -0
- data/examples/images/module_graph.jpg +0 -0
- data/examples/images/rgl_modules.png +0 -0
- data/examples/{insel-der-tausend-gefahren.rb → insel_der_tausend_gefahren.rb} +18 -19
- data/examples/north.rb +2 -2
- data/examples/north2.rb +11 -11
- data/examples/rdep-rgl.rb +218 -222
- data/lib/rgl/adjacency.rb +78 -74
- data/lib/rgl/base.rb +160 -78
- data/lib/rgl/bellman_ford.rb +115 -0
- data/lib/rgl/bidirectional.rb +17 -10
- data/lib/rgl/bipartite.rb +87 -0
- data/lib/rgl/condensation.rb +13 -4
- data/lib/rgl/connected_components.rb +38 -30
- data/lib/rgl/dijkstra.rb +158 -0
- data/lib/rgl/dijkstra_visitor.rb +42 -0
- data/lib/rgl/dot.rb +40 -32
- data/lib/rgl/edge_properties_map.rb +55 -0
- data/lib/rgl/edmonds_karp.rb +136 -0
- data/lib/rgl/enumerable_ext.rb +4 -1
- data/lib/rgl/graph_iterator.rb +15 -0
- data/lib/rgl/graph_visitor.rb +138 -0
- data/lib/rgl/graph_wrapper.rb +15 -0
- data/lib/rgl/graphxml.rb +20 -10
- data/lib/rgl/implicit.rb +68 -66
- data/lib/rgl/mutable.rb +37 -31
- data/lib/rgl/path_builder.rb +40 -0
- data/lib/rgl/prim.rb +52 -0
- data/lib/rgl/rdot.rb +411 -374
- data/lib/rgl/topsort.rb +23 -16
- data/lib/rgl/transitivity.rb +29 -27
- data/lib/rgl/traversal.rb +67 -205
- data/rakelib/dep_graph.rake +4 -3
- data/test/bellman_ford_test.rb +187 -0
- data/test/bipartite_test.rb +47 -0
- data/test/components_test.rb +80 -0
- data/test/cycles_test.rb +60 -0
- data/test/dijkstra_test.rb +148 -0
- data/test/directed_graph_test.rb +118 -0
- data/test/dot_test.rb +26 -0
- data/test/edge_properties_map_test.rb +63 -0
- data/test/edge_test.rb +35 -0
- data/test/edmonds_karp_test.rb +105 -0
- data/{tests/TestGraph.rb → test/graph_test.rb} +6 -6
- data/test/graph_xml_test.rb +57 -0
- data/test/implicit_test.rb +53 -0
- data/test/prim_test.rb +98 -0
- data/{tests/TestRdot.rb → test/rdot_test.rb} +309 -308
- data/{tests → test}/test_helper.rb +4 -1
- data/{tests/TestTransitivity.rb → test/transitivity_test.rb} +43 -43
- data/test/traversal_test.rb +221 -0
- data/test/undirected_graph_test.rb +103 -0
- metadata +226 -145
- data/examples/example.jpg +0 -0
- data/examples/module_graph.jpg +0 -0
- data/install.rb +0 -49
- data/tests/TestComponents.rb +0 -65
- data/tests/TestCycles.rb +0 -61
- data/tests/TestDirectedGraph.rb +0 -125
- data/tests/TestDot.rb +0 -18
- data/tests/TestEdge.rb +0 -34
- data/tests/TestGraphXML.rb +0 -57
- data/tests/TestImplicit.rb +0 -52
- data/tests/TestTraversal.rb +0 -220
- data/tests/TestUnDirectedGraph.rb +0 -102
data/lib/rgl/dijkstra.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'rgl/dijkstra_visitor'
|
2
|
+
require 'rgl/edge_properties_map'
|
3
|
+
require 'rgl/path_builder'
|
4
|
+
|
5
|
+
require 'delegate'
|
6
|
+
require 'algorithms'
|
7
|
+
|
8
|
+
module RGL
|
9
|
+
|
10
|
+
class DijkstraAlgorithm
|
11
|
+
|
12
|
+
# Distance combinator is a lambda that accepts the distance (usually from the source) to vertex _u_ and the weight
|
13
|
+
# of the edge connecting vertex _u_ to another vertex _v_ and returns the distance to vertex _v_ if it's reached
|
14
|
+
# through the vertex _u_. By default, the distance to vertex _u_ and the edge's weight are summed.
|
15
|
+
DEFAULT_DISTANCE_COMBINATOR = lambda { |distance, edge_weight| distance + edge_weight }
|
16
|
+
|
17
|
+
# Initializes Dijkstra's algorithm for a _graph_ with provided edges weights map.
|
18
|
+
#
|
19
|
+
def initialize(graph, edge_weights_map, visitor, distance_combinator = nil)
|
20
|
+
@graph = graph
|
21
|
+
@edge_weights_map = build_edge_weights_map(edge_weights_map)
|
22
|
+
@visitor = visitor
|
23
|
+
@distance_combinator = distance_combinator || DEFAULT_DISTANCE_COMBINATOR
|
24
|
+
end
|
25
|
+
|
26
|
+
# Finds the shortest path from the _source_ to the _target_ in the graph.
|
27
|
+
#
|
28
|
+
# Returns the shortest path, if it exists, as an Array of vertices. Otherwise, returns nil.
|
29
|
+
#
|
30
|
+
def shortest_path(source, target)
|
31
|
+
init(source)
|
32
|
+
relax_edges(target, true)
|
33
|
+
PathBuilder.new(source, @visitor.parents_map).path(target)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Finds the shortest path form the _source_ to every other vertex of the graph and builds shortest paths map.
|
37
|
+
#
|
38
|
+
# Returns the shortest paths map that contains the shortest path (if it exists) from the source to any vertex of the
|
39
|
+
# graph.
|
40
|
+
#
|
41
|
+
def shortest_paths(source)
|
42
|
+
find_shortest_paths(source)
|
43
|
+
PathBuilder.new(source, @visitor.parents_map).paths(@graph.vertices)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Finds the shortest path from the _source_ to every other vertex.
|
47
|
+
#
|
48
|
+
def find_shortest_paths(source)
|
49
|
+
init(source)
|
50
|
+
relax_edges
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def init(source)
|
56
|
+
@visitor.set_source(source)
|
57
|
+
|
58
|
+
@queue = Queue.new
|
59
|
+
@queue.push(source, 0)
|
60
|
+
end
|
61
|
+
|
62
|
+
def relax_edges(target = nil, break_on_target = false)
|
63
|
+
until @queue.empty?
|
64
|
+
u = @queue.pop
|
65
|
+
|
66
|
+
break if break_on_target && u == target
|
67
|
+
|
68
|
+
@visitor.handle_examine_vertex(u)
|
69
|
+
|
70
|
+
@graph.each_adjacent(u) do |v|
|
71
|
+
relax_edge(u, v) unless @visitor.finished_vertex?(v)
|
72
|
+
end
|
73
|
+
|
74
|
+
@visitor.color_map[u] = :BLACK
|
75
|
+
@visitor.handle_finish_vertex(u)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def relax_edge(u, v)
|
80
|
+
@visitor.handle_examine_edge(u, v)
|
81
|
+
|
82
|
+
new_v_distance = @distance_combinator.call(@visitor.distance_map[u], @edge_weights_map.edge_property(u, v))
|
83
|
+
|
84
|
+
if new_v_distance < @visitor.distance_map[v]
|
85
|
+
old_v_distance = @visitor.distance_map[v]
|
86
|
+
|
87
|
+
@visitor.distance_map[v] = new_v_distance
|
88
|
+
@visitor.parents_map[v] = u
|
89
|
+
|
90
|
+
if @visitor.color_map[v] == :WHITE
|
91
|
+
@visitor.color_map[v] = :GRAY
|
92
|
+
@queue.push(v, new_v_distance)
|
93
|
+
elsif @visitor.color_map[v] == :GRAY
|
94
|
+
@queue.decrease_key(v, old_v_distance, new_v_distance)
|
95
|
+
end
|
96
|
+
|
97
|
+
@visitor.handle_edge_relaxed(u, v)
|
98
|
+
else
|
99
|
+
@visitor.handle_edge_not_relaxed(u, v)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_edge_weights_map(edge_weights_map)
|
104
|
+
edge_weights_map.is_a?(EdgePropertiesMap) ? edge_weights_map : NonNegativeEdgePropertiesMap.new(edge_weights_map, @graph.directed?)
|
105
|
+
end
|
106
|
+
|
107
|
+
class Queue < SimpleDelegator # :nodoc:
|
108
|
+
|
109
|
+
def initialize
|
110
|
+
@heap = Containers::Heap.new { |a, b| a.distance < b.distance }
|
111
|
+
super(@heap)
|
112
|
+
end
|
113
|
+
|
114
|
+
def push(vertex, distance)
|
115
|
+
@heap.push(vertex_key(vertex, distance), vertex)
|
116
|
+
end
|
117
|
+
|
118
|
+
def decrease_key(vertex, old_distance, new_distance)
|
119
|
+
@heap.change_key(vertex_key(vertex, old_distance), vertex_key(vertex, new_distance))
|
120
|
+
end
|
121
|
+
|
122
|
+
def vertex_key(vertex, distance)
|
123
|
+
VertexKey.new(vertex, distance)
|
124
|
+
end
|
125
|
+
|
126
|
+
VertexKey = Struct.new(:vertex, :distance)
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end # class DijkstraAlgorithm
|
131
|
+
|
132
|
+
module Graph
|
133
|
+
|
134
|
+
# Finds the shortest path from the _source_ to the _target_ in the graph.
|
135
|
+
#
|
136
|
+
# If the path exists, returns it as an Array of vertices. Otherwise, returns nil.
|
137
|
+
#
|
138
|
+
# Raises ArgumentError if edge weight is negative or undefined.
|
139
|
+
#
|
140
|
+
def dijkstra_shortest_path(edge_weights_map, source, target, visitor = DijkstraVisitor.new(self))
|
141
|
+
DijkstraAlgorithm.new(self, edge_weights_map, visitor).shortest_path(source, target)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Finds the shortest paths from the _source_ to each vertex of the graph.
|
145
|
+
#
|
146
|
+
# Returns a Hash that maps each vertex of the graph to an Array of vertices that represents the shortest path
|
147
|
+
# from the _source_ to the vertex. If the path doesn't exist, the corresponding hash value is nil. For the _source_
|
148
|
+
# vertex returned hash contains a trivial one-vertex path - [source].
|
149
|
+
#
|
150
|
+
# Raises ArgumentError if edge weight is negative or undefined.
|
151
|
+
#
|
152
|
+
def dijkstra_shortest_paths(edge_weights_map, source, visitor = DijkstraVisitor.new(self))
|
153
|
+
DijkstraAlgorithm.new(self, edge_weights_map, visitor).shortest_paths(source)
|
154
|
+
end
|
155
|
+
|
156
|
+
end # module Graph
|
157
|
+
|
158
|
+
end # module RGL
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rgl/base'
|
2
|
+
require 'rgl/graph_visitor'
|
3
|
+
|
4
|
+
module RGL
|
5
|
+
|
6
|
+
# Dijkstra shortest path algorithm has the following event points:
|
7
|
+
#
|
8
|
+
# * examine_vertex
|
9
|
+
# * examine_edge
|
10
|
+
# * edge_relaxed
|
11
|
+
# * edge_not_relaxed
|
12
|
+
# * finish_vertex
|
13
|
+
#
|
14
|
+
class DijkstraVisitor
|
15
|
+
|
16
|
+
include GraphVisitor
|
17
|
+
|
18
|
+
attr_accessor :distance_map, :parents_map
|
19
|
+
|
20
|
+
def_event_handlers :edge_relaxed, :edge_not_relaxed
|
21
|
+
|
22
|
+
# Returns visitor into initial state.
|
23
|
+
#
|
24
|
+
def reset
|
25
|
+
super
|
26
|
+
|
27
|
+
@distance_map = Hash.new(INFINITY)
|
28
|
+
@parents_map = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Initializes visitor with a new source.
|
32
|
+
#
|
33
|
+
def set_source(source)
|
34
|
+
reset
|
35
|
+
|
36
|
+
color_map[source] = :GRAY
|
37
|
+
distance_map[source] = 0
|
38
|
+
end
|
39
|
+
|
40
|
+
end # DijkstraVisitor
|
41
|
+
|
42
|
+
end # RGL
|
data/lib/rgl/dot.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# dot.rb
|
2
2
|
#
|
3
|
-
# $Id
|
3
|
+
# $Id$
|
4
4
|
#
|
5
5
|
# Minimal Dot support, based on Dave Thomas's dot module (included in rdoc).
|
6
6
|
# rdot.rb is a modified version which also contains support for undirected
|
@@ -12,50 +12,57 @@ module RGL
|
|
12
12
|
|
13
13
|
module Graph
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
# Return a RGL::DOT::Digraph for directed graphs or a DOT::Graph for an
|
16
|
+
# undirected Graph. _params_ can contain any graph property specified in
|
17
|
+
# rdot.rb.
|
18
|
+
#
|
19
|
+
def to_dot_graph(params = {})
|
20
|
+
params['name'] ||= self.class.name.gsub(/:/, '_')
|
21
|
+
fontsize = params['fontsize'] ? params['fontsize'] : '8'
|
22
|
+
graph = (directed? ? DOT::Digraph : DOT::Graph).new(params)
|
23
|
+
edge_class = directed? ? DOT::DirectedEdge : DOT::Edge
|
18
24
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
25
|
+
each_vertex do |v|
|
26
|
+
name = v.to_s
|
27
|
+
graph << DOT::Node.new(
|
28
|
+
'name' => name,
|
29
|
+
'fontsize' => fontsize,
|
30
|
+
'label' => name
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
each_edge do |u, v|
|
35
|
+
graph << edge_class.new(
|
36
|
+
'from' => u.to_s,
|
37
|
+
'to' => v.to_s,
|
38
|
+
'fontsize' => fontsize
|
39
|
+
)
|
34
40
|
end
|
41
|
+
|
35
42
|
graph
|
36
43
|
end
|
37
44
|
|
38
45
|
# Output the DOT-graph to stream _s_.
|
39
|
-
|
40
|
-
def print_dotted_on
|
46
|
+
#
|
47
|
+
def print_dotted_on(params = {}, s = $stdout)
|
41
48
|
s << to_dot_graph(params).to_s << "\n"
|
42
49
|
end
|
43
50
|
|
44
51
|
# Call dotty[http://www.graphviz.org] for the graph which is written to the
|
45
|
-
# file 'graph.dot' in the
|
46
|
-
|
47
|
-
def dotty
|
52
|
+
# file 'graph.dot' in the current directory.
|
53
|
+
#
|
54
|
+
def dotty(params = {})
|
48
55
|
dotfile = "graph.dot"
|
49
|
-
File.open(dotfile, "w")
|
56
|
+
File.open(dotfile, "w") do |f|
|
50
57
|
print_dotted_on(params, f)
|
51
|
-
|
58
|
+
end
|
52
59
|
system("dotty", dotfile)
|
53
60
|
end
|
54
61
|
|
55
62
|
# Use dot[http://www.graphviz.org] to create a graphical representation of
|
56
|
-
# the graph.
|
57
|
-
|
58
|
-
def write_to_graphic_file
|
63
|
+
# the graph. Returns the filename of the graphics file.
|
64
|
+
#
|
65
|
+
def write_to_graphic_file(fmt='png', dotfile="graph")
|
59
66
|
src = dotfile + ".dot"
|
60
67
|
dot = dotfile + "." + fmt
|
61
68
|
|
@@ -63,9 +70,10 @@ module RGL
|
|
63
70
|
f << self.to_dot_graph.to_s << "\n"
|
64
71
|
end
|
65
72
|
|
66
|
-
system(
|
73
|
+
system("dot -T#{fmt} #{src} -o #{dot}")
|
67
74
|
dot
|
68
75
|
end
|
69
76
|
|
70
|
-
end
|
71
|
-
|
77
|
+
end # module Graph
|
78
|
+
|
79
|
+
end # module RGL
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RGL
|
2
|
+
|
3
|
+
class EdgePropertiesMap
|
4
|
+
|
5
|
+
def initialize(edge_properties_map, directed)
|
6
|
+
@edge_properties_map = edge_properties_map
|
7
|
+
@directed = directed
|
8
|
+
|
9
|
+
check_properties
|
10
|
+
end
|
11
|
+
|
12
|
+
def edge_property(u, v)
|
13
|
+
if @directed
|
14
|
+
property = @edge_properties_map[[u, v]]
|
15
|
+
else
|
16
|
+
property = @edge_properties_map[[u, v]] || @edge_properties_map[[v, u]]
|
17
|
+
end
|
18
|
+
|
19
|
+
validate_property(property, u, v)
|
20
|
+
|
21
|
+
property
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def check_properties
|
27
|
+
@edge_properties_map.each { |(u, v), property| validate_property(property, u, v) } if @edge_properties_map.respond_to?(:each)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_property(property, u, v)
|
31
|
+
report_missing_property(property, u, v)
|
32
|
+
end
|
33
|
+
|
34
|
+
def report_missing_property(property, u, v)
|
35
|
+
raise ArgumentError.new("property of edge (#{u}, #{v}) is not defined") unless property
|
36
|
+
end
|
37
|
+
|
38
|
+
end # EdgePropertiesMap
|
39
|
+
|
40
|
+
class NonNegativeEdgePropertiesMap < EdgePropertiesMap
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def validate_property(property, u, v)
|
45
|
+
super
|
46
|
+
report_negative_property(property, u, v)
|
47
|
+
end
|
48
|
+
|
49
|
+
def report_negative_property(property, u, v)
|
50
|
+
raise ArgumentError.new("property of edge (#{u}, #{v}) is negative") if property < 0
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'rgl/edge_properties_map'
|
2
|
+
require 'rgl/traversal'
|
3
|
+
|
4
|
+
module RGL
|
5
|
+
|
6
|
+
class EdmondsKarpAlgorithm
|
7
|
+
|
8
|
+
# Initializes Edmonds-Karp algorithm for a _graph_ with provided edges capacities map.
|
9
|
+
#
|
10
|
+
def initialize(graph, edge_capacities_map)
|
11
|
+
raise NotDirectedError.new('Edmonds-Karp algorithm can only be applied to a directed graph') unless graph.directed?
|
12
|
+
|
13
|
+
@graph = graph
|
14
|
+
validate_edge_capacities(edge_capacities_map)
|
15
|
+
@edge_capacities_map = NonNegativeEdgePropertiesMap.new(edge_capacities_map, true)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Finds the maximum flow from the _source_ to the _sink_ in the graph.
|
19
|
+
#
|
20
|
+
# Returns flows map as a hash that maps each edge of the graph to a flow through that edge that is required to reach
|
21
|
+
# the maximum total flow.
|
22
|
+
#
|
23
|
+
def maximum_flow(source, sink)
|
24
|
+
raise ArgumentError.new("source and sink can't be equal") if source == sink
|
25
|
+
|
26
|
+
@flow_map = Hash.new(0)
|
27
|
+
@residual_capacity_map = lambda { |u, v| @edge_capacities_map.edge_property(u, v) - @flow_map[[u, v]] }
|
28
|
+
|
29
|
+
loop do
|
30
|
+
bfs = EdmondsKarpBFSIterator.new(@graph, source, sink, @residual_capacity_map)
|
31
|
+
|
32
|
+
bfs.move_forward_until { bfs.color_map[sink] == :GRAY }
|
33
|
+
|
34
|
+
if bfs.color_map[sink] == :WHITE
|
35
|
+
break # no more augmenting paths
|
36
|
+
else
|
37
|
+
min_residual_capacity = INFINITY
|
38
|
+
|
39
|
+
augmenting_path = [sink]
|
40
|
+
|
41
|
+
while augmenting_path.first != source
|
42
|
+
v = augmenting_path.first
|
43
|
+
u = bfs.parents_map[v]
|
44
|
+
|
45
|
+
augmenting_path.unshift(u)
|
46
|
+
min_residual_capacity = [min_residual_capacity, @residual_capacity_map[u, v]].min
|
47
|
+
end
|
48
|
+
|
49
|
+
augmenting_path.each_cons(2) do |(u, v)|
|
50
|
+
@flow_map[[u, v]] += min_residual_capacity
|
51
|
+
@flow_map[[v, u]] -= min_residual_capacity
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@flow_map
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def validate_edge_capacities(edge_capacities_map)
|
62
|
+
@graph.each_edge do |u, v|
|
63
|
+
raise ArgumentError.new("reverse edge for (#{u}, #{v}) is missing") unless @graph.has_edge?(v, u)
|
64
|
+
validate_capacity(u, v, edge_capacities_map)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_capacity(u, v, edge_capacities_map)
|
69
|
+
capacity = get_capacity(u, v, edge_capacities_map)
|
70
|
+
reverse_capacity = get_capacity(v, u, edge_capacities_map)
|
71
|
+
|
72
|
+
validate_negative_capacity(u, v, capacity)
|
73
|
+
validate_negative_capacity(v, u, reverse_capacity)
|
74
|
+
|
75
|
+
raise ArgumentError.new("either (#{u}, #{v}) or (#{v}, #{u}) should have 0 capacity") unless [capacity, reverse_capacity].include?(0)
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_capacity(u, v, edge_capacities_map)
|
79
|
+
edge_capacities_map.fetch([u, v]) { raise ArgumentError.new("capacity for edge (#{u}, #{v}) is missing") }
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate_negative_capacity(u, v, capacity)
|
83
|
+
raise ArgumentError.new("capacity of edge (#{u}, #{v}) is negative") unless capacity >= 0
|
84
|
+
end
|
85
|
+
|
86
|
+
class EdmondsKarpBFSIterator < BFSIterator
|
87
|
+
|
88
|
+
attr_accessor :parents_map
|
89
|
+
|
90
|
+
def initialize(graph, start, stop, residual_capacities)
|
91
|
+
super(graph, start)
|
92
|
+
@residual_capacities = residual_capacities
|
93
|
+
@stop_vertex = stop
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset
|
97
|
+
super
|
98
|
+
@parents_map = {}
|
99
|
+
end
|
100
|
+
|
101
|
+
def follow_edge?(u, v)
|
102
|
+
# follow only edges with positive residual capacity
|
103
|
+
super && @residual_capacities[u, v] > 0
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_tree_edge(u, v)
|
107
|
+
super
|
108
|
+
@parents_map[v] = u
|
109
|
+
end
|
110
|
+
|
111
|
+
end # class EdmondsKarpBFSIterator
|
112
|
+
|
113
|
+
end # class EdmondsKarpAlgorithm
|
114
|
+
|
115
|
+
module Graph
|
116
|
+
|
117
|
+
# Finds the maximum flow from the _source_ to the _sink_ in the graph.
|
118
|
+
#
|
119
|
+
# Returns flows map as a hash that maps each edge of the graph to a flow through that edge that is required to reach
|
120
|
+
# the maximum total flow.
|
121
|
+
#
|
122
|
+
# For the method to work, the graph should be first altered so that for each directed edge (u, v) it contains reverse
|
123
|
+
# edge (u, v). Capacities of the primary edges should be non-negative, while reverse edges should have zero capacity.
|
124
|
+
#
|
125
|
+
# Raises ArgumentError if the graph is not directed.
|
126
|
+
#
|
127
|
+
# Raises ArgumentError if a reverse edge is missing, edge capacity is missing, an edge has negative capacity, or a
|
128
|
+
# reverse edge has positive capacity.
|
129
|
+
#
|
130
|
+
def maximum_flow(edge_capacities_map, source, sink)
|
131
|
+
EdmondsKarpAlgorithm.new(self, edge_capacities_map).maximum_flow(source, sink)
|
132
|
+
end
|
133
|
+
|
134
|
+
end # module Graph
|
135
|
+
|
136
|
+
end
|