【问题标题】:Reconstructing the shortest path in a graph in BFS在 BFS 中重建图中的最短路径
【发布时间】:2021-04-18 02:00:50
【问题描述】:

我正在使用 BFS 在 ruby​​ 中实现一个函数,我想知道如何在无向图中打印起始值和结束值之间的最短路径

图表 在这个例子中,图有十个顶点。每个节点代表一个顶点,它有一个存储相邻节点(边)的数组。

$ 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]

预期输出

2,1,9 或 2,4,9 等

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

节点

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

图表

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

测试

graph = generate_random_graph    
graph.to_s
bfs_shortest_path(graph,2,9)

【问题讨论】:

  • 你也可以发布Graph 结构吗?这意味着您将编写的节点初始化部分!
  • 您应该在入队时记录一个顶点已被访问过。 (这包括在循环之前将起始顶点设置为已访问。)同时记录父顶点,即导致顶点入队的current_node_key。然后当你找到结束顶点时,你可以跟随父母回到起点重新创建路径。请注意,您可以使用父列表来跟踪已访问的顶点。已访问具有父节点的顶点。
  • @MaruthiAdithya 完成谢谢
  • @ggorlen 完成。通过函数 generate_random_graph。谢谢
  • @ggorlen 完美!谢谢

标签: ruby algorithm breadth-first-search


【解决方案1】:

感谢cmets

工作版

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

【讨论】:

    【解决方案2】:

    要保留您去过的地方的历史记录,以便您可以重建路径,而不是 visited 集,请使用哈希。哈希跟踪哪个节点是每个访问节点的前任。当您将邻居推入队列时,将我们要离开的当前节点添加为父节点/前任节点came_from[neighbor] = current

    这个哈希值也可以像visited 一样用于消除循环。

    当你达到目标时,你可以通过反复键入这个came_from 哈希来重建路径,直到你用完前辈。反转数组(线性时间)并将其作为最终路径返回。

    这是一个最小的、可运行的示例,您可以根据自己的类结构进行调整:

    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"]
    

    【讨论】:

    • 非常聪明的解决方案。谢谢
    • @ipegasus 因此,您可以投票并接受答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多