Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Dijkstra shortest path algorithm. #5

Merged
merged 17 commits into from
Jan 30, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 6 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
coverage/
pkg/
rgl/
wiki*
/coverage
/doc
/pkg
/rgl
/TAGS
/tags
graph.dot
.project
Gemfile.lock
/TAGS
/tags
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@ language: ruby
rvm:
- 1.8.7
- 1.9.3
- jruby-18mode
- jruby-19mode
script: 'bundle exec rake test'
script: "script/test_all 2>&1"
1 change: 0 additions & 1 deletion examples/insel_der_tausend_gefahren.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Die Insel der 1000 Gefahren
# http://www.amazon.de/1000-Gefahren-Die-Insel/dp/3473520225/ref=pd_sim_b?ie=UTF8&qid=1203279845&sr=8-3

require 'rubygems' rescue nil
require 'rgl/adjacency'
require 'rgl/implicit'
require 'rgl/dot'
Expand Down
6 changes: 3 additions & 3 deletions lib/rgl/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class NotUndirectedError < RuntimeError; end
class NoVertexError < IndexError; end
class NoEdgeError < IndexError; end

# Module Edge includes classes for representing egdes of directed and
# Module Edge includes classes for representing edges of directed and
# undirected graphs. There is no need for a Vertex class, because every ruby
# object can be a vertex of a graph.
#
Expand All @@ -25,7 +25,7 @@ module Edge
# Simply a directed pair (source -> target). Most library functions try do
# omit to instantiate edges. They instead use two vertex parameters for
# representing edges (see each_edge). If a client wants to store edges
# explicitly DirecteEdge or UnDirectedEdge instances are returned
# explicitly DirectedEdge or UnDirectedEdge instances are returned
# (i.e. Graph#edges).
#
class DirectedEdge
Expand Down Expand Up @@ -233,7 +233,7 @@ def size # Why not in Enumerable?
inject(0) { |n, v| n + 1 }
end

alias :num_vertices :size
alias num_vertices size

# Returns the number of edges.
#
Expand Down
236 changes: 236 additions & 0 deletions lib/rgl/dijkstra.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
require 'rgl/base'
require 'rgl/graph_visitor'

require 'delegate'
require 'algorithms'

module RGL

# Dijkstra shortest path algorithm has the following event points:
#
# * examine_vertex
# * examine_edge
# * edge_relaxed
# * edge_not_relaxed
# * finish_vertex
#
class DijkstraVisitor

INFINITY = 1.0 / 0.0 # positive infinity

include GraphVisitor

attr_accessor :distance_map, :parent_map

def_event_handlers :edge_relaxed, :edge_not_relaxed

# Returns visitor into initial state.
#
def reset
super

@distance_map = Hash.new(INFINITY)
@parent_map = {}
end

# Returns true if the _vertex_ can be reached from the source.
#
def reachable?(vertex)
distance_map[vertex] < INFINITY
end

end

class DijkstraAlgorithm

# Initializes Dijkstra algorithm for a _graph_ with provided edges weights map.
#
def initialize(graph, edge_weights_map, visitor)
@graph = graph
@edge_weights_map = edge_weights_map
@visitor = visitor
@queue = Queue.new
end

# Finds the shortest path from the _source_ to the _target_ in the graph.
#
# Returns the shortest path, if it exists, as an Array of vertices. Otherwise, returns nil.
#
def shortest_path(source, target)
init(source)
relax_edges(target, true)
restore_path(source, target)
end

# Finds the shortest path form the _source_ to every other vertex of the graph.
#
# Returns parents map that can be used to restore the shortest path, if it exists, from the _source_ to any vertext.
#
def shortest_paths(source)
init(source)
relax_edges
restore_paths(source)
end

private

def reset
@visitor.reset
@queue.clear
end

def init(source)
reset

@visitor.color_map[source] = :GRAY
@visitor.distance_map[source] = 0

@queue.push(source, @visitor.distance_map[source])
end

def relax_edges(target = nil, break_on_target = false)
until @queue.empty?
u = @queue.pop

break if break_on_target && u == target

@visitor.handle_examine_vertex(u)

@graph.each_adjacent(u) do |v|
next if @visitor.finished_vertex?(v)

if relax_edge(u, v)
@visitor.handle_edge_relaxed(u, v)
else
@visitor.handle_edge_not_relaxed(u, v)
end
end

@visitor.color_map[u] = :BLACK
@visitor.handle_finish_vertex(u)
end
end

def relax_edge(u, v)
@visitor.handle_examine_edge(u, v)

new_v_distance = @visitor.distance_map[u] + edge_weight(u, v)

if new_v_distance < @visitor.distance_map[v]
old_v_distance = @visitor.distance_map[v]

@visitor.distance_map[v] = new_v_distance
@visitor.parent_map[v] = u

if @visitor.color_map[v] == :WHITE
@visitor.color_map[v] = :GRAY
@queue.push(v, new_v_distance)
elsif @visitor.color_map[v] == :GRAY
@queue.decrease_key(v, old_v_distance, new_v_distance)
end

true
else
false
end
end

def edge_weight(u, v)
if @graph.directed?
@edge_weights_map[[u, v]]
else
@edge_weights_map[[u, v]] || @edge_weights_map[[v, u]]
end
end

def restore_path(source, target)
PathBuilder.new(source, @visitor.parent_map).path(target)
end

def restore_paths(source)
path_builder = PathBuilder.new(source, @visitor.parent_map)

@graph.each_vertex do |vertex|
path_builder.path(vertex)
end

path_builder.paths
end

class PathBuilder # :nodoc:

attr_reader :paths

def initialize(source, parent_map)
@source = source
@parent_map = parent_map
@paths = {}
end

def path(target)
if @paths.has_key?(target)
@paths[target]
else
@paths[target] = restore_path(target)
end
end

private

def restore_path(target)
return [@source] if target == @source

parent = @parent_map[target]
path(parent) + [target] if parent
end

end

class Queue < SimpleDelegator # :nodoc:

def initialize
@heap = Containers::Heap.new { |a, b| a.distance < b.distance }
super(@heap)
end

def push(vertex, distance)
@heap.push(vertex_key(vertex, distance), vertex)
end

def decrease_key(vertex, old_distance, new_distance)
@heap.change_key(vertex_key(vertex, old_distance), vertex_key(vertex, new_distance))
end

def vertex_key(vertex, distance)
VertexKey.new(vertex, distance)
end

VertexKey = Struct.new(:vertex, :distance)

end

end # class DijkstraAlgorithm

module Graph

# Finds the shortest path from the _source_ to the _target_ in the graph.
#
# If the path exists, returns it as an Array of vertices. Otherwise, returns nil.
#
def dijkstra_shortest_path(edge_weights_map, source, target, visitor = DijkstraVisitor.new(self))
DijkstraAlgorithm.new(self, edge_weights_map, visitor).shortest_path(source, target)
end

# Finds the shortest paths from the _source_ to each vertex of the graph.
#
# Returns a Hash that maps each vertex of the graph to an Array of vertices that represents the shortest path
# from the _source_ to the vertex. If the path doesn't exist, the corresponding hash value is nil. For the _source_
# vertex returned hash contains a trivial one-vertex path - [source].
#
def dijkstra_shortest_paths(edge_weights_map, source, visitor = DijkstraVisitor.new(self))
DijkstraAlgorithm.new(self, edge_weights_map, visitor).shortest_paths(source)
end

end # module Graph

end # module RGL
15 changes: 15 additions & 0 deletions lib/rgl/graph_iterator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'stream'

require 'rgl/graph_wrapper'

module RGL

# A GraphIterator is the abstract basis for all Iterators on graphs.
# Each graph iterator should implement the protocol defined in module Stream.
#
module GraphIterator
include Stream
include GraphWrapper
end

end # RGL