简体   繁体   中英

What's a proper algorithm to count all possible paths from any given vertex to another in a graph?

I'm working with graphs and I'd like to count all possible paths from given vertex X to given vertex Y.

This is the algorithm I've come up with:

class Graph
  def paths_from(vertex_a, vertex_b, count = 0, visited = Array.new(@vertices.length, false))
    return count if @vertices.none?(vertex_b) || @vertices.none?(vertex_a)

    count += 1 if vertex_a == vertex_b
    visited[@vertices.index(vertex_a)] = true
    @net[@vertices.index(vertex_a)].each do |vertex|
      paths_from(vertex, vertex_b, count, visited) unless visited[@vertices.index(vertex)]
    end

    count
  end
end

Using recursion, I'm expecting to traverse df through the graph. However, I keep getting 0 instead of the expected value given below graph:

describe Graph do
  context 'can output all possible from vertex a to vertex b.' do
    let(:subject) { Graph.new(%w[a b c d e]) }
    before(:each) do
      subject.add_edge(0, 1)
      subject.add_edge(0, 2)
      subject.add_edge(0, 4)
      subject.add_edge(1, 2)
      subject.add_edge(1, 4)
      subject.add_edge(2, 3)
      subject.add_edge(3, 1)
    end
  
    it 'example #1' do
      expect(subject.paths_from('a', 'f')).to eql 0 # => should output 0 and it does.
    end

    it 'example #2' do
      expect(subject.paths_from('f', 'a')).to eql 0 # => should ouput 0 and it does.
    end

    it 'example #3' do
      expect(subject.paths_from('a', 'b')).to eql 2
    end
  end
end

Doubt #1: I've checked geeksforgeeks approach tips regarding the algorithm: it states I should backtrack. What is that and how may I do it? I guess they're referencing the visited variable... but I've got no clue as to how to do that.

I'll drop the class definition just in case.

class Graph
attr_accessor :net, :vertices

  def initialize(vertices = [])
    @net = Array.new(vertices.length) { [] }
    @vertices = vertices
  end

  def add_edge(vertex_a, vertex_b)
    return if @net[vertex_a].nil? || @vertices[vertex_b].nil?

    @net[vertex_a] << @vertices[vertex_b]
  end
end

Doubt #2: If I print the count variable right before the @net loop, it prints 0, then '1' four times, yet it returns 0. Why is that? I suppose it's because it's returing #paths_from's first call... if that's the case, how may I return #paths_from's last call's count variable?

You don't appear to be recording the output of the recursion.

can you try something like:

count += paths_from(vertex, vertex_b, count, visited) unless visited[@vertices.index(vertex)]

not passing in a count at all:

class Graph
attr_accessor :net, :vertices, :visited

  def initialize(vertices = [])
    @net = Array.new(vertices.length) { [] }
    @visited = Array.new(vertices.length, false)
    @vertices = vertices
  end

  def add_edge(vertex_a, vertex_b)
    return if @net[vertex_a].nil? || @vertices[vertex_b].nil?

    @net[vertex_a] << @vertices[vertex_b]
  end

  def paths_from(vertex_a, vertex_b)
    return 0 if @vertices.none?(vertex_b) || @vertices.none?(vertex_a)
    count = 0

    count += 1 if vertex_a == vertex_b
    @visited[@vertices.index(vertex_a)] = true
    @net[@vertices.index(vertex_a)].each do |vertex|
      count += paths_from(vertex, vertex_b) unless @visited[@vertices.index(vertex)]
    end

    count
  end
end

I assume the graph is directed and contains no cycles.

Suppose the graph is described by the following hash.

graph = { :A=>[:C, :D], :B=> [:D], :C=>[:D, :E, :F], :D=>[:G], :E=>[:F, :H],
          :F=>[:D, :G, :I, :H], :G=>[:H, :I], :H=>[], :I=>[] } 

The nodes are keys and the arcs are given by the keys and each element of a keys value. There are, for example, arcs :A->:C , :A-->:D , :B->:D and so on.

We can display this graph as follows.

在此处输入图像描述

Given two of the nodes, designated as the origin and terminus , the problem is to determine the number of paths from origin to terminus .

Suppose

origin   = :A
terminus = :H

It is seen that there are nine paths from A to H:

A-C-E-H
A-C-E-F-H
A-C-E-F-G-H
A-C-E-F-D-G-H
A-C-F-H
A-C-F-D-G-H
A-C-F-G-H
A-C-D-G-H
A-D-G-H

I will give two solutions. The first is a recursion that requires little code but enumerates all paths. The number of such paths, however, can grow exponentially with the number of nodes. The second is more complex but is much faster for larger graphs. It's computational complexity appears to be only O(n 2 ), where n is the number of nodes.

Enumerate paths from origin to destination

def tot_paths(graph, terminus, node)
  graph[node].reduce(0) do |tot, n|
    tot + ((n == terminus) ? 1 : tot_paths(graph, terminus, n))
  end
end
tot_paths(graph, :H, :A)
  #=> 9

More complex, but a much more efficient solution

The second approach requires two steps. The first is to perform a topological sort of the nodes of the graph.

Any array sorted that is an array of topologically-sorted nodes has the property that, for any pair of nodes ni = sorted[i] and nj = sorted[j] there is no path from nj to ni if j > i . A directed graph with no cycles is guaranteed to have at least one topological sort of nodes.

I have used Kuhn's algorithm (described at the above link) to produce the topological sort given by the following array:

[:B, :A, :C, :E, :F, :D, :G, :I, :H]

As shown below, if these nodes are viewed as being on a line, all arcs are directed from left to right. (For now disregard the numbers shown above the nodes.)

在此处输入图像描述

My implementation of the Kuhn algorithm is as follows.

nodes = graph.keys
  #=> [:A, :B, :C, :D, :E, :F, :G, :H, :I] 
incoming = graph.each_with_object(nodes.map { |n| [n, []] }.to_h) do |(k,v),h|
  v.each { |n| h[n] << k }
end
  #=> {:A=>[], :B=>[], :C=>[:A], :D=>[:A, :B, :C, :F], :E=>[:C], :F=>[:C, :E],
  #    :G=>[:D, :F], :H=>[:E, :F, :G], :I=>[:F, :G]}

incoming[:H] #=> [:E, :F, :G] , for example, shows that the arcs directed into node :H are :E->:H , :F->:H and :G->:H .

no_incoming_nodes = incoming.select { |k,v| v.empty? }.keys
  #=> [:A, :B]  
sorted_nodes = []
until no_incoming_nodes.empty?
  n = no_incoming_nodes.pop
  sorted_nodes << n
  graph[n].each do |next_node|
    incoming[next_node].delete(n)
    no_incoming_nodes << next_node if incoming[next_node].empty?
  end
end
sorted_nodes
  #=> [:B, :A, :C, :E, :F, :D, :G, :I, :H]

The second step is to implement a dynamic-programming algorithm to count the number of paths from node :A to node :H . I will explain how it works by explaining the meaning of the numbers above each node in the diagram immediatally above.

The number of paths from node I (the element of sorted_nodes that is followed by the terminus) is 0 (the number above I) because I is not the terminus and has no outgoing nodes.

Going back one node in sorted_nodes , the number of paths from G to the terminus is 1 as it is followed by the terminus ( 1 ) and node I, which has 0 paths to the terminus.

The number of paths from node D to the terminus is 1 because D is followed by only one node, G, and G has 1 path to the terminus.

Node F has 3 paths to the terminus, 1 that goes directly to the terminus, 1 that passes through D, 0 that pass through I and 1 that passes through G.

Similarly, there are 4 paths from node E to the terminus, 8 paths from node C to the terminus and, our objective, 9 paths from A, the origin, to the terminus. The computation can be implemented as follows (using sorted_nodes computed earlier).

origin   = :A
terminus = :H

tot_to = graph.each_with_object({}) do |(k,v),h|  
  (h[k] = k == terminus ? 1 : 0) if v.empty?
end
  #=> {:H=>1, :I=>0}

(sorted_nodes.index(terminus) - 1).downto(sorted_nodes.index(origin)).each do |i|
  n = sorted_nodes[i]
  tot_to[n] = graph[n].sum { |m| tot_to[m] }
end
tot_to[origin]
  #=> 9

Lastly, I would like to mention that the dynamic programming algorithm could have been organised differently, with roughly equal computational efficiency. Rather than beginning at the terminus and working backward, we could have started at the origin and worked forward until the terminus is reached, at each node computing the number of paths from A to the given node.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM