I am implementing a function in ruby using BFS, and I would like to know how to print the shortest path in an undirected graph, between a start value and end value
Graph In this example the graph has ten vertices. Each node represents a vertex and it has an array that stores adjacent nodes (edges).
$ irb
graph.to_s
1. 1 -> [2 3 4 5 8 9 10]
2. 2 -> [1 4 5 6 7 8 10]
3. 3 -> [1 4 6]
4. 4 -> [1 2 3 6 7 8 9 10]
5. 5 -> [1 2 8 9 10]
6. 6 -> [2 3 4 7 8 9 10]
7. 7 -> [2 4 6 8 9]
8. 8 -> [1 2 4 5 6 7 9 10]
9. 9 -> [1 4 5 6 7 8]
10. 10 -> [1 2 4 5 6 8]
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Expected Output
2,1,9 or 2,4,9, etc.
BFS
def bfs_shortest_path(graph, start=2, search=9)
if graph.nodes[start].nil? || graph.nodes[search].nil?
return nil
end
visited = Set.new
search_queue = Queue.new
search_queue.enq(start)
while !search_queue.empty? do
current_node_key = search_queue.deq
current_node = graph.nodes[current_node_key]
visited.add(current_node_key)
if current_node.value == search
return current_node # I would like to return the PATH Eg. 2,1,9
end
adjacent_nodes_array = current_node.adjacent_nodes.map{|x| x.value}
adjacent_nodes_array.each do |value|
if !visited.include?(value)
search_queue.enq(value)
graph.nodes[value].concat_to_path(current_node.path_from_start)
end
end
end
end
Node
class Node
attr_reader :value
attr_reader :adjacent_nodes
def initialize(value)
@value = value
@adjacent_nodes = []
end
def add_edge(node)
@adjacent_nodes.push(node)
end
def to_s
"#{@value} -> [#{@adjacent_nodes.map(&:value).sort.join(" ")}]"
end
end
Graph
class Graph
attr_reader :nodes
def initialize
@nodes = {}
end
def add_node(node)
@nodes[node.value] = node
end
def add_edge(node1,node2)
if @nodes[node1.value].adjacent_nodes.map(&:value).include? (node2.value)
puts "#{node1.value} and #{node2.value} already have an edge"
elsif node1.value == node2.value
puts "node1.value == node2.value"
else
@nodes[node1.value].add_edge(@nodes[node2.value])
@nodes[node2.value].add_edge(@nodes[node1.value])
end
end
def to_s
@nodes.keys.sort.each_with_index do |key,index|
puts "#{index + 1}. #{@nodes[key].to_s}"
end
end
end
Generating a Graph
def generate_random_graph
g = Graph.new
[*1..10].shuffle.each do |node_value|
g.add_node(Node.new(node_value))
end
40.times do
key1 = g.nodes.keys.sample
key2 = g.nodes.keys.sample
g.add_edge(g.nodes[key1],g.nodes[key2])
end
return g
end
Test
graph = generate_random_graph
graph.to_s
bfs_shortest_path(graph,2,9)
To keep a history of where you've been so you can reconstruct a path, instead of a visited
set, use a hash. The hash tracks which node is the predecessor of each visited node. When you push a neighbor onto the queue, add the current node we're moving away from as the parent/predecessor by came_from[neighbor] = current
.
This hash also works for eliminating cycles as visited
does.
When you reach the goal, you can rebuild the path by repeatedly keying into this came_from
hash until you run out of predecessors. Reverse the array (linear time) and return it as the final path.
Here's a minimal, runnable example you can adapt to your class structure:
def reconstruct_path(tail, came_from)
path = []
while !tail.nil?
path << tail
tail = came_from[tail]
end
path.reverse
end
def bfs(graph, start, goal)
q = Queue.new
q.enq(start)
came_from = {start => nil}
while !q.empty?
curr = q.deq
if graph.key? curr
return reconstruct_path(goal, came_from) if curr == goal
graph[curr].each do |neighbor|
if !came_from.key?(neighbor)
came_from[neighbor] = curr
q.enq(neighbor)
end
end
end
end
end
graph = {
"A" => ["B", "C"],
"B" => ["A", "F"],
"C" => ["D"],
"D" => ["E", "F"],
"E" => [],
"F" => []
}
=begin
+----+
v |
A--->B--->F
| ^
V |
C--->D----+
|
v
E
=end
p bfs(graph, "A", "F") # => ["A", "B", "F"]
p bfs(graph, "A", "E") # => ["A", "C", "D", "E"]
p bfs(graph, "B", "E") # => ["B", "A", "C", "D", "E"]
Thanks for the comments
Working Version
class Node
attr_reader :value
attr_reader :adjacent_nodes
attr_reader :path_from_start
def initialize(value)
@value = value
@adjacent_nodes = []
@path_from_start = []
end
def add_edge(node)
@adjacent_nodes.push(node)
end
def add_to_path(value)
@path_from_start.push(value)
end
def concat_to_path(value_array)
@path_from_start.concat(value_array)
end
def to_s
"#{@value} -> [#{@adjacent_nodes.map(&:value).sort.join(" ")}]"
end
end
class Graph
attr_reader :nodes
def initialize
@nodes = {}
end
def add_node(node)
@nodes[node.value] = node
end
def add_edge(node1,node2)
if @nodes[node1.value].adjacent_nodes.map(&:value).include? (node2.value)
puts "#{node1.value} and #{node2.value} already have an edge"
elsif node1.value == node2.value
puts "node1.value == node2.value"
else
@nodes[node1.value].add_edge(@nodes[node2.value])
@nodes[node2.value].add_edge(@nodes[node1.value])
end
end
def to_s
@nodes.keys.sort.each_with_index do |key,index|
puts "#{index + 1}. #{@nodes[key].to_s}"
end
end
end
def generate_random_graph
g = Graph.new
[*1..10].shuffle.each do |node_value|
g.add_node(Node.new(node_value))
end
40.times do
key1 = g.nodes.keys.sample
key2 = g.nodes.keys.sample
g.add_edge(g.nodes[key1],g.nodes[key2])
end
return g
end
def bfs(graph, start_node_value=2, search_value=9)
if graph.nodes[start_node_value].nil? || graph.nodes[search_value].nil?
return nil
end
visited = Set.new
search_queue = Queue.new
search_queue.enq(graph.nodes[start_node_value])
while !search_queue.empty? do
current_node = search_queue.deq
visited.add(current_node)
if current_node.value == search_value
return current_node
end
current_node.adjacent_nodes.each do |node|
if !visited.include?(graph.nodes[node.value])
search_queue.enq(graph.nodes[node.value])
end
end
end
end
def bfs_shortest_path(graph, start=2, search=9)
if graph.nodes[start].nil? || graph.nodes[search].nil?
return nil
end
visited = Set.new
visited.add(start)
search_queue = Queue.new
search_queue.enq(start)
while !search_queue.empty? do
current_node_key = search_queue.deq
current_node = graph.nodes[current_node_key]
current_node.add_to_path(current_node.value)
if current_node.value == search
return current_node.path_from_start
end
adjacent_nodes_array = current_node.adjacent_nodes.map{|x| x.value}
adjacent_nodes_array.each do |value|
if !visited.include?(value)
search_queue.enq(value)
visited.add(value)
graph.nodes[value].concat_to_path(current_node.path_from_start)
end
end
end
end
def test_graph
graph = generate_random_graph
graph.to_s
bfs_shortest_path(graph,2,9)
end
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.