Ruby-从给定的起点查找图形中的所有路径

时间:2019-10-30 08:22:48

标签: ruby graph traversal

哈希图填充如下:

{"1"=>["2"], "2"=>["3", "7"], "3"=>["4"], "5"=>["6", "2"], "6"=>["7"], "7"=>["8", "4"]}

,以便每个键可以具有多个值。这些值代表一组路线上的公交车站,例如从公交车站1可以到达2,从公交车站2可以到达3或7,依此类推。

我正在尝试遍历此图结构,并从给定的起点查找长度大于1的所有可能路径。例如,对于起点1,所有可能路径的列表将为

[[1,2] , [1,2,3] , [1,2,3,4] , [1,2,7] , [1,2,7,8], [1,2,7,4]]

我试图递归地解决此问题,在该迭代中,我遍历当前键的子级(该键的值)并调用一个实质上扩展该键的子级的函数。到目前为止,这是我的代码:

if hash.has_key?(origin.to_s)
  tmp = origin.to_s
  tmp << hash[origin.to_s][0]
  paths.push(tmp)
  expand_node(hash,paths,paths.length-1,tmp[tmp.length-1])
end

起点是起点。首先,我将第一个路径(在这种情况下为[1,2])添加到名为paths的数组中,然后展开添加到前一个路径(在这种情况下为2)中的最后一个值。

def expand_node(hash,paths,curr_paths_index,node)
curr_node = node
if hash.has_key?(node.to_s)
  counter = 0
  hash[curr_node].each do |child|
    tmp = paths[curr_paths_index]
    tmp << child
    paths << tmp
    curr_paths_index += counter + 1
    puts paths
    expand_node(hash,paths,curr_paths_index,child)
  end
end

结束

自变量curr_paths_index跟踪我扩展的路径。 参数path是当前找到的路径的完整列表。 参数node是要扩展的当前值。

函数完成后打印paths会产生:

123 123 1234 1234 1234 12347 12347 12347 12347 123478 123478 123478 123478 123478 1234784 1234784 1234784 1234784 1234784 1234784

有什么方法可以修改此代码,以便产生所需的输出(如上所示)?总的来说,有没有更好的方法来解决这个问题?

谢谢。

3 个答案:

答案 0 :(得分:2)

解决递归问题的一种方法是先将其分解为易于解决的单个案例,然后将其归纳。这是一个更简单的情况:

  

给出一个图和该图的路径,确定可以将哪些节点添加到该路径的末尾而无需创建循环。

如果我们能够解决这个问题,我们也可以轻松解决更大的问题。

  1. 从只是您的起始节点的路径开始
  2. 查找可添加到该路径的所有节点而无需创建循环
  3. 对于每个这样的节点,输出一个路径,该路径是您的初始路径加上该节点,然后...
  4. 使用新路径从第2步开始递归重复

请注意,如果在步骤2中找不到新节点,则由于第3步和第4步无关,因此递归调用将终止。

这是我解决步骤2的方法,我将递归部分留给您

def find_next_nodes graph, path
  graph[path[-1]].reject { |node| path.include? node }
end

答案 1 :(得分:1)

我会这样做,很确定可以进一步优化它,并且性能可能也会出现问题。

require 'set'

dt = {"1"=>["2"], "2"=>["3", "7"], "3"=>["4"], "5"=>["6", "2"], "6"=>["7"], "7"=>["8", "4"]}

def traverse(initial_hash, key_arrays, traversed_keys = [])
  value = []
  key_arrays.map do |key|
    n = [*traversed_keys, key]
    value << n unless n.count == 1 # prevent first key to be added
    another_keys = initial_hash.fetch(key, nil) # try to load the value, fallback to nil if key not found
    if another_keys.nil? # stop condition
      value << n
    elsif another_keys.is_a?(Enumerable) # if key found
      another_keys.map do |ank| # recursive
        value.concat([*traverse(initial_hash, [ank], n)]) # splat operator to unpack the array
      end
    end
  end
  value.to_set # only return unique value, can be converted to array if needed
end

traverse(dt, [1].map(&:to_s)).map { |val| val.join('') }

对不起,但是我还没有调试您的代码,所以我认为temp变量存在问题,因为无法扩展的路径将继续到下一次迭代。

答案 2 :(得分:1)

def doit(graph, start)
  return [] unless graph.key?(start)
  recurse(graph, start).drop(1)
end

def recurse(graph, start)
  (graph[start] || []).each_with_object([[start]]) { |next_node, arr|
    recurse(graph, next_node).each { |a| arr << [start, *a] } }
end

graph = { 1=>[2], 2=>[3, 7], 3=>[4], 5=>[6, 2], 6=>[7], 7=>[8, 4] }

doit(graph, 1)
  #=> [[1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 7], [1, 2, 7, 8],
  #    [1, 2, 7, 4]] 
doit(graph, 2)
  #=> [[2, 3], [2, 3, 4], [2, 7], [2, 7, 8], [2, 7, 4]] 
doit(graph, 3)
  #=> [[3, 4]] 
doit(graph, 5)
  #=> [[5, 6], [5, 6, 7], [5, 6, 7, 8], [5, 6, 7, 4], [5, 2],
  #    [5, 2, 3], [5, 2, 3, 4], [5, 2, 7], [5, 2, 7, 8], [5, 2, 7, 4]] 
doit(graph, 6)
  #=> [[6, 7], [6, 7, 8], [6, 7, 4]] 
doit(graph, 7)
  #=> [[7, 8], [7, 4]]     
doit(graph, 4)
  #=> [] 
doit(graph, 99)
  #=> []