【问题标题】:Find all paths between two graph nodes查找两个图节点之间的所有路径
【发布时间】:2012-03-21 02:31:25
【问题描述】:

我正在研究 Dijkstras 算法的实现,以检索路由网络上互连节点之间的最短路径。我有实施工作。当我将起始节点传递给算法时,它会返回所有节点的所有最短路径。

我的问题: 如何检索从节点 A 到节点 G 的所有可能路径,甚至是从节点 A 到节点 A 的所有可能路径

【问题讨论】:

  • 好吧,如果你的图表有循环,那可能是一个非常长的列表。
  • 您想要不重复顶点/边的路径吗?
  • @HexTree 我不太清楚你的意思。每个顶点都是唯一的。我基本上是在寻找每条路径的权重以及通过每条路径触及的节点数
  • 为什么要查找所有路径?如果您的问题是在某些节点发生故障等情况下如何重新路由,则有一些算法(启发式)。但你目前的情况非常普遍,而且是 np-hard。
  • @Paul 请考虑我的新答案,这是您问题的可靠解决方案

标签: algorithm graph-theory graph-algorithm breadth-first-search


【解决方案1】:

找到所有可能的路径是一个难题,因为简单路径的数量呈指数级增长。甚至找到第 k 个最短路径 [或最长路径] 都是 NP-Hard

找到从st 的所有路径[或直到一定长度的所有路径] 的一种可能解决方案是BFS,不保留visited 集,或者对于加权版本 - 您可能想要使用uniform cost search

请注意,在每个具有循环的图中[它不是DAG],st 之间的路径可能是无限的。

【讨论】:

  • 谢谢,我会尝试查看 BFS 或统一成本搜索
  • @Paul:不客气。只需确保在他们两个中都没有使用visited 集合[就像原始算法建议的那样],否则您只会获得部分路径。此外,您应该将路径限制为一定长度以避免无限循环[如果图形有循环......]。祝你好运!
  • @VShreyas 这有点老了,答案明确地说“所有路径都达到一定长度”,这可以通过 BFS 完成而无需访问集。如果您想要两个节点之间的所有简单路径,您可以使用带有“本地”访问集的 DFS 来实现(当它回溯时从访问集中删除一个节点)。
  • @GarethRees 假设两个节点之间的kth 最短简单路径存在多项式时间(非伪多项式)算法。由于最多有(3/2)n!这样的路径,所以可以进行二分查找,看看是否有长度为n的简单路径。由于log{(3/2)n!}n 中的多项式,因此编码所需的重复次数和所需的重复次数都是输入大小的多项式。由于算法的输入也以多项式时间运行,因此总运行时间是多项式的,答案是节点之间是否存在哈密顿路径。因此,如果存在这样的算法,P=NP。
  • @GarethRees 附录:路径数,选择路径中的节点数(i=1,....,n),对于每个i,选择(n, i) 节点,并对它们重新排序 (i!),giving: (3/2)n!.
【解决方案2】:

您通常不想这样做,因为在非平凡图中它们的数量是指数级的;如果你真的想得到所有(简单)路径,或者所有(简单)循环,你只需找到一个(通过走图),然后回溯到另一个。

【讨论】:

  • 它简单、高效且适用于任何 DAG。你在误导@Paul。
【解决方案3】:

我想你想找到“简单”的路径(如果没有节点出现不止一次,那么路径就是简单的,可能除了第一个和最后一个)。

由于问题是 NP 难题,您可能需要进行深度优先搜索的变体。

基本上,从 A 生成所有可能的路径并检查它们是否以 G 结尾。

【讨论】:

    【解决方案4】:

    我认为你想要的是某种形式的基于 BFS 的 Ford-Fulkerson 算法。它用于通过查找两个节点之间的所有增广路径来计算网络的最大流量。

    http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm

    【讨论】:

      【解决方案5】:

      我会给你一个(有点小)版本的(虽然我认为可以理解)科学证明,你不能在可行的时间内做到这一点。

      我要证明的是,在任意图形G 中枚举两个选定的不同节点(例如,st)之间的所有简单路径的时间复杂度不是多项式。请注意,由于我们只关心这些节点之间的路径数量,因此边缘成本并不重要。

      当然,如果图表有一些精心选择的属性,这很容易。不过我正在考虑一般情况。


      假设我们有一个多项式算法,列出了st 之间的所有简单路径。

      如果G 已连接,则列表非空。如果G 不是并且st 在不同的组件中,那么列出它们之间的所有路径真的很容易,因为没有!如果它们在同一个组件中,我们可以假设整个图仅由该组件组成。所以让我们假设G 确实是连接的。

      列出的路径的数量必须是多项式的,否则算法无法将它们全部返回给我。如果它枚举所有这些,它必须给我最长的一个,所以它就在那里。有了路径列表,可以应用一个简单的过程来指出最长的路径。

      我们可以证明(尽管我想不出一种有凝聚力的说法)这条最长的路径必须遍历G 的所有顶点。因此,我们刚刚找到了一个带有多项式过程的Hamiltonian Path!但这是一个众所周知的 NP-hard 问题。

      然后我们可以得出结论,我们认为我们拥有的这个多项式算法不太可能存在,除非P = NP

      【讨论】:

      • 如果我理解正确,那么该证明仅适用于无向图,因为在有向图中,“这条最长的路径必须遍历 G 的所有顶点”的断言不一定成立。对吗?
      • 嗯,是的,但是您可以使用您的算法以类似的方式回答是否存在有向哈密顿路径,这也是 NP 完全的。如果你的答案是n-1,那就有。如果不是,那么就不可能有这样的路径,否则它会比你已知的最长。
      • 只是为了清楚。如果有向版本可以在多时间内解决,那么它的答案将给出有向哈密顿路径的答案。此外,如果我们有加权边,可以证明通过多项式过程我们可以回答旅行商问题。
      【解决方案6】:

      我已经实现了一个版本,它基本上可以找到从一个节点到另一个节点的所有可能路径,但它不计算任何可能的“循环”(我使用的图表是循环的)。所以基本上,没有一个节点会在同一路径中出现两次。如果图表是非周期性的,那么我想你可以说它似乎找到了两个节点之间的所有可能路径。它似乎工作得很好,对于我大约 150 的图形大小,它几乎可以立即在我的机器上运行,尽管我确信运行时间必须是指数级的,所以它会开始变慢,因为图变大了。

      这里有一些 Java 代码演示了我已经实现的内容。我相信一定有更有效或更优雅的方法来做到这一点。

      Stack connectionPath = new Stack();
      List<Stack> connectionPaths = new ArrayList<>();
      // Push to connectionsPath the object that would be passed as the parameter 'node' into the method below
      void findAllPaths(Object node, Object targetNode) {
          for (Object nextNode : nextNodes(node)) {
              if (nextNode.equals(targetNode)) {
                  Stack temp = new Stack();
                  for (Object node1 : connectionPath)
                  temp.add(node1);
                  connectionPaths.add(temp);
              } else if (!connectionPath.contains(nextNode)) {
                  connectionPath.push(nextNode);
                  findAllPaths(nextNode, targetNode);
                  connectionPath.pop();
              }
          }
      }
      

      【讨论】:

      • 这个有非递归版本吗?
      • 我没有,不,但我认为理论上,任何递归程序都可以转换为非递归程序,我认为通过使用堆栈对象之类的东西,重点是我相信,使用程序的堆栈空间来模拟递归程序实际上在做什么。可以查一下递归程序转非递归的原理。
      【解决方案7】:

      Here 是一种算法,使用 DFS 的修改来查找和打印从 s 到 t 的所有路径。还可以使用动态编程来查找所有可能路径的计数。伪代码如下所示:

      AllPaths(G(V,E),s,t)
       C[1...n]    //array of integers for storing path count from 's' to i
       TopologicallySort(G(V,E))  //here suppose 's' is at i0 and 't' is at i1 index
      
        for i<-0 to n
            if i<i0
                C[i]<-0  //there is no path from vertex ordered on the left from 's' after the topological sort
            if i==i0
               C[i]<-1
            for j<-0 to Adj(i)
                C[i]<- C[i]+C[j]
      
       return C[i1]
      

      【讨论】:

        【解决方案8】:

        有一篇不错的文章可以回答您的问题/只是它打印路径而不是收集它们/。 请注意,您可以在在线 IDE 中试验 C++/Python 示例。

        http://www.geeksforgeeks.org/find-paths-given-source-destination/

        【讨论】:

          【解决方案9】:

          find_paths[s, t, d, k]

          这个问题现在有点老了......但我会把我的帽子扔进戒指。

          我个人发现find_paths[s, t, d, k] 形式的算法很有用,其中:

          • s是起始节点
          • t 是目标节点
          • d 是搜索的最大深度
          • k 是要查找的路径数

          dk 使用您的编程语言的无穷大形式将为您提供所有路径§。

          § 显然,如果您使用的是有向图,并且您希望 st 之间的所有 无向 路径,则必须以两种方式运行:

          find_paths[s, t, d, k] <join> find_paths[t, s, d, k]
          

          辅助函数

          我个人喜欢递归,虽然它有时会很困难,但首先让我们定义我们的辅助函数:

          def find_paths_recursion(graph, current, goal, current_depth, max_depth, num_paths, current_path, paths_found)
            current_path.append(current)
          
            if current_depth > max_depth:
              return
          
            if current == goal:
              if len(paths_found) <= number_of_paths_to_find:
                paths_found.append(copy(current_path))
          
              current_path.pop()
              return
          
            else:
              for successor in graph[current]:
              self.find_paths_recursion(graph, successor, goal, current_depth + 1, max_depth, num_paths, current_path, paths_found)
          
            current_path.pop()
          

          主要功能

          除此之外,核心功能是微不足道的:

          def find_paths[s, t, d, k]:
            paths_found = [] # PASSING THIS BY REFERENCE  
            find_paths_recursion(s, t, 0, d, k, [], paths_found)
          

          首先,让我们注意一些事情:

          • 上面的伪代码是多种语言的混搭——但与 python 最相似(因为我只是在其中编写代码)。严格的复制粘贴是行不通的。
          • [] 是一个未初始化的列表,请将其替换为您选择的编程语言的等效项
          • paths_foundreference 传递。很明显,递归函数不返回任何东西。妥善处理。
          • 这里graph 假设某种形式的hashed 结构。有很多方法可以实现图。无论哪种方式,graph[vertex] 都会为您提供有向图中的相邻顶点列表 - 进行相应调整。
          • 这假设您已进行预处理以移除“带扣”(自环)、循环和多边

          【讨论】:

            【解决方案10】:

            如果您真的关心从最短路径到最长路径的路径排序,那么使用修改后的 A* 或 Dijkstra 算法会好得多。稍作修改,算法将按照最短路径的顺序返回尽可能多的可能路径。因此,如果您真正想要的是从最短到最长排序的所有可能路径,那么这就是要走的路。

            如果您想要一个基于 A* 的实现能够返回从最短到最长排序的所有路径,以下将实现这一点。它有几个优点。首先,它可以有效地从最短到最长排序。此外,它仅在需要时计算每个附加路径,因此如果您因为不需要每条路径而提前停止,您可以节省一些处理时间。每次计算下一条路径时,它还会为后续路径重用数据,因此效率更高。最后,如果您找到一些所需的路径,您可以提前中止以节省一些计算时间。总的来说,如果您关心按路径长度排序,这应该是最有效的算法。

            import java.util.*;
            
            public class AstarSearch {
                private final Map<Integer, Set<Neighbor>> adjacency;
                private final int destination;
            
                private final NavigableSet<Step> pending = new TreeSet<>();
            
                public AstarSearch(Map<Integer, Set<Neighbor>> adjacency, int source, int destination) {
                    this.adjacency = adjacency;
                    this.destination = destination;
            
                    this.pending.add(new Step(source, null, 0));
                }
            
                public List<Integer> nextShortestPath() {
                    Step current = this.pending.pollFirst();
                    while( current != null) {
                        if( current.getId() == this.destination )
                            return current.generatePath();
                        for (Neighbor neighbor : this.adjacency.get(current.id)) {
                            if(!current.seen(neighbor.getId())) {
                                final Step nextStep = new Step(neighbor.getId(), current, current.cost + neighbor.cost + predictCost(neighbor.id, this.destination));
                                this.pending.add(nextStep);
                            }
                        }
                        current = this.pending.pollFirst();
                    }
                    return null;
                }
            
                protected int predictCost(int source, int destination) {
                    return 0; //Behaves identical to Dijkstra's algorithm, override to make it A*
                }
            
                private static class Step implements Comparable<Step> {
                    final int id;
                    final Step parent;
                    final int cost;
            
                    public Step(int id, Step parent, int cost) {
                        this.id = id;
                        this.parent = parent;
                        this.cost = cost;
                    }
            
                    public int getId() {
                        return id;
                    }
            
                    public Step getParent() {
                        return parent;
                    }
            
                    public int getCost() {
                        return cost;
                    }
            
                    public boolean seen(int node) {
                        if(this.id == node)
                            return true;
                        else if(parent == null)
                            return false;
                        else
                            return this.parent.seen(node);
                    }
            
                    public List<Integer> generatePath() {
                        final List<Integer> path;
                        if(this.parent != null)
                            path = this.parent.generatePath();
                        else
                            path = new ArrayList<>();
                        path.add(this.id);
                        return path;
                    }
            
                    @Override
                    public int compareTo(Step step) {
                        if(step == null)
                            return 1;
                        if( this.cost != step.cost)
                            return Integer.compare(this.cost, step.cost);
                        if( this.id != step.id )
                            return Integer.compare(this.id, step.id);
                        if( this.parent != null )
                            this.parent.compareTo(step.parent);
                        if(step.parent == null)
                            return 0;
                        return -1;
                    }
            
                    @Override
                    public boolean equals(Object o) {
                        if (this == o) return true;
                        if (o == null || getClass() != o.getClass()) return false;
                        Step step = (Step) o;
                        return id == step.id &&
                            cost == step.cost &&
                            Objects.equals(parent, step.parent);
                    }
            
                    @Override
                    public int hashCode() {
                        return Objects.hash(id, parent, cost);
                    }
                }
            
               /*******************************************************
               *   Everything below here just sets up your adjacency  *
               *   It will just be helpful for you to be able to test *
               *   It isnt part of the actual A* search algorithm     *
               ********************************************************/
            
                private static class Neighbor {
                    final int id;
                    final int cost;
            
                    public Neighbor(int id, int cost) {
                        this.id = id;
                        this.cost = cost;
                    }
            
                    public int getId() {
                        return id;
                    }
            
                    public int getCost() {
                        return cost;
                    }
                }
            
                public static void main(String[] args) {
                    final Map<Integer, Set<Neighbor>> adjacency = createAdjacency();
                    final AstarSearch search = new AstarSearch(adjacency, 1, 4);
                    System.out.println("printing all paths from shortest to longest...");
                    List<Integer> path = search.nextShortestPath();
                    while(path != null) {
                        System.out.println(path);
                        path = search.nextShortestPath();
                    }
                }
            
                private static Map<Integer, Set<Neighbor>> createAdjacency() {
                    final Map<Integer, Set<Neighbor>> adjacency = new HashMap<>();
            
                    //This sets up the adjacencies. In this case all adjacencies have a cost of 1, but they dont need to.
                    addAdjacency(adjacency, 1,2,1,5,1);         //{1 | 2,5}
                    addAdjacency(adjacency, 2,1,1,3,1,4,1,5,1); //{2 | 1,3,4,5}
                    addAdjacency(adjacency, 3,2,1,5,1);         //{3 | 2,5}
                    addAdjacency(adjacency, 4,2,1);             //{4 | 2}
                    addAdjacency(adjacency, 5,1,1,2,1,3,1);     //{5 | 1,2,3}
            
                    return Collections.unmodifiableMap(adjacency);
                }
            
                private static void addAdjacency(Map<Integer, Set<Neighbor>> adjacency, int source, Integer... dests) {
                    if( dests.length % 2 != 0)
                        throw new IllegalArgumentException("dests must have an equal number of arguments, each pair is the id and cost for that traversal");
            
                    final Set<Neighbor> destinations = new HashSet<>();
                    for(int i = 0; i < dests.length; i+=2)
                        destinations.add(new Neighbor(dests[i], dests[i+1]));
                    adjacency.put(source, Collections.unmodifiableSet(destinations));
                }
            }
            

            上述代码的输出如下:

            [1, 2, 4]
            [1, 5, 2, 4]
            [1, 5, 3, 2, 4]
            

            请注意,每次您调用nextShortestPath() 时,它都会根据需要为您生成下一条最短路径。它只计算所需的额外步骤,不会遍历任何旧路径两次。此外,如果您决定不需要所有路径并提前结束执行,您就可以节省大量的计算时间。你只计算你需要的路径数量,而不是更多。

            最后应该指出的是,A* 和 Dijkstra 算法确实有一些小的限制,尽管我认为它不会影响你。也就是说,它不能在权重为负的图上正常工作。

            这里是 JDoodle 的链接,您可以在该链接中自己在浏览器中运行代码并查看其运行情况。您还可以更改图表以显示它也适用于其他图表:http://jdoodle.com/a/ukx

            【讨论】:

              【解决方案11】:

              以下函数(使用两个节点之间的递归寻路函数修改的BFS)将为无环图完成工作:

              from collections import defaultdict
              
              # modified BFS
              def find_all_parents(G, s):
                  Q = [s]
                  parents = defaultdict(set)
                  while len(Q) != 0:
                      v = Q[0]
                      Q.pop(0)
                      for w in G.get(v, []):
                          parents[w].add(v)
                          Q.append(w)
                  return parents
              
              # recursive path-finding function (assumes that there exists a path in G from a to b)   
              def find_all_paths(parents, a, b):
                  return [a] if a == b else [y + b for x in list(parents[b]) for y in find_all_paths(parents, a, x)]
              

              例如,用下面的图(DAGG 给出

              G = {'A':['B','C'], 'B':['D'], 'C':['D', 'F'], 'D':['E', 'F'], 'E':['F']}
              

              如果我们想找到节点'A''F'之间的所有路径(使用上面定义的函数作为find_all_paths(find_all_parents(G, 'A'), 'A', 'F')),它将返回以下路径:

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-08-21
                • 2014-07-09
                • 2012-12-15
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多