找到从A到Z的所有路径的高效算法?

时间:2011-12-01 13:24:19

标签: java algorithm

使用一组random inputs这样的(20k行):

A B
U Z
B A
A C
Z A
K Z
A Q
D A
U K
P U
U P
B Y
Y R
Y U
C R
R Q
A D
Q Z

查找从A到Z的所有路径。

  1. A - B - Y - R - Q - Z
  2. A - B - Y - U - Z
  3. A - C - R - Q - Z
  4. A - Q - Z
  5. A - B - Y - U - K - Z
  6. enter image description here

    位置在路径中不能出现多次,因此A - B - Y - U - P - U - Z无效。

    位置被命名为AAA到ZZZ(为简单起见,此处显示为A - Z)并且输入是随机的,这样可能有也可能没有位置ABC,所有位置可能是XXX(不太可能),或者那里可能不是所有地方的可能路径都是“孤立的”。

    最初我认为这是未加权shortest path problem的变体,但我发现它有所不同,我不确定算法在这里是如何应用的。

    我目前的解决方案是这样的:

    1. 预处理列表,以便我们有一个将位置(左)指向位置列表(右侧)的散列图

    2. 创建一个hashmap来跟踪“访问过的位置”。创建一个列表来存储“找到的路径”。

    3. 将X(起始位置)存储到“已访问位置”散列图。

    4. 在第一个hashmap中搜索X,(位置A将在O(1)时间内给我们(B,C,Q)。

    5. 对于每个找到的位置(B,C,Q),检查它是否是最终目的地(Z)。如果存储,则将其存储在“找到的路径”列表中。否则,如果“访问位置”散列映射中尚不存在,则现在重新启动到步骤3,该位置为“X”。 (以下实际代码)

    6. 使用此当前解决方案,永远将{strong>所有(非最短)可能的路线从“BKI”映射到this provided data的“SIN”。< / p>

      我想知道是否有更有效(时间)的方式。有没有人知道一个更好的算法来找到从任意位置A到任意位置Z的所有路径?

      当前解决方案的实际代码:

      import java.util.*;
      import java.io.*;
      
      public class Test {
          private static HashMap<String, List<String>> left_map_rights;
      
          public static void main(String args[]) throws Exception {
              left_map_rights = new HashMap<>();
              BufferedReader r = new BufferedReader(new FileReader("routes.text"));
              String line;
              HashMap<String, Void> lines = new HashMap<>();
              while ((line = r.readLine()) != null) {
                  if (lines.containsKey(line)) { // ensure no duplicate lines
                      continue;
                  }
                  lines.put(line, null);
                  int space_location = line.indexOf(' ');
                  String left = line.substring(0, space_location);
                  String right = line.substring(space_location + 1);
                  if(left.equals(right)){ // rejects entries whereby left = right
                      continue;
                  }
                  List<String> rights = left_map_rights.get(left);
                  if (rights == null) {
                      rights = new ArrayList<String>();
                      left_map_rights.put(left, rights);
                  }
                  rights.add(right);
              }
              r.close();
              System.out.println("start");
              List<List<String>> routes = GetAllRoutes("BKI", "SIN");
              System.out.println("end");
              for (List<String> route : routes) {
                  System.out.println(route);
              }
          }
      
          public static List<List<String>> GetAllRoutes(String start, String end) {
              List<List<String>> routes = new ArrayList<>();
              List<String> rights = left_map_rights.get(start);
              if (rights != null) {
                  for (String right : rights) {
                      List<String> route = new ArrayList<>();
                      route.add(start);
                      route.add(right);
                      Chain(routes, route, right, end);
                  }
              }
              return routes;
          }
      
          public static void Chain(List<List<String>> routes, List<String> route, String right_most_currently, String end) {
              if (right_most_currently.equals(end)) {
                  routes.add(route);
                  return;
              }
              List<String> rights = left_map_rights.get(right_most_currently);
              if (rights != null) {
                  for (String right : rights) {
                      if (!route.contains(right)) {
                          List<String> new_route = new ArrayList<String>(route);
                          new_route.add(right);
                          Chain(routes, new_route, right, end);
                      }
                  }
              }
          }
      }
      

4 个答案:

答案 0 :(得分:13)

据我了解你的问题,Dijkstras算法不能按原样应用,因为每个定义的最短路径问题在所有可能路径的集合中找到单个路径。您的任务是找到所有路径本身。

Dijkstras算法的许多优化都涉及以更高的成本切断搜索树。您将无法在搜索中切断这些部分,因为您需要所有的发现。

我认为你的意思是所有路径不包括圈子

算法:

  • 将网络泵入2xim数组26x26的布尔/整数。的FromTo [I,J]。 为现有链接设置1 / true。

  • 从第一个节点开始跟踪所有后续节点(搜索链接为1 / true)。

  • 将访问过的节点保留在某个结构中(数组/列表)。由于最大 深度似乎是26,这应该可以通过递归。

  • 正如@soulcheck在下面写的那样,你可能会考虑削减你所看到的路径。您可以在阵列的每个元素中保留指向目标的路径列表。相应地调整断裂条件。

  • 中断时间

    • 访问结束节点(存储结果)
    • 访问之前已经访问过的节点(循环)
    • 访问您已找到目的地所有路径的节点,并将当前路径与该节点中的所有现有路径合并。

性能明智我投票反对使用散列图和列表,而不喜欢静态结构。

嗯,在重读这个问题时,我意识到节点的名称不能局限于A-Z。你正在写一些大约20k行,有26个字母,一个完全连接的A-Z网络需要更少的链接。也许你跳过递归和静态结构:)

好的,从AAA到ZZZ的有效名称,数组会变得太大。因此,您最好为网络创建动态结构。反问题:关于性能,我的算法需要的popuplate数组的最佳数据结构是什么?我投票支持2 dim ArrayList。任何人吗?

答案 1 :(得分:7)

你提出的是DFS的方案,只有回溯。这是正确的,除非你想允许循环路径(你没有指明你是否这样做。)

但是有两个陷阱。

  1. 您必须密切关注当前路径上已访问过的节点(以消除周期)
  2. 您必须知道在回溯时如何选择下一个节点,这样当您在当前路径上访问时,您不会在图中的同一子树上下降。
  3. 伪代码或多或少如下:

    getPaths(A, current_path) :
        if (A is destination node): return [current_path]
        for B = next-not-visited-neighbor(A) : 
            if (not B already on current path) 
                result = result + getPaths(B, current_path + B)
        return result 
    
     list_of_paths =  getPaths(A, [A])
    

    这几乎就是你所说的。

    但是要小心,因为在完整图表中查找所有路径非常耗费时间和内存。

    修改 为了澄清,该算法在最坏的情况下具有Ω(n!)时间复杂度,因为它必须在大小为n的完整图中列出从一个顶点到另一个顶点的所有路径,并且至少有(n-2)!形式的路径&lt; A,除A和Z之外的所有节点的排列,Z&gt;。如果只列出结果会花费更多的话,就无法做到更好。

答案 2 :(得分:1)

您的数据本质上是adjacency list,它允许您构建一个以A对应的节点为根的树。以获取A&amp; A之间的所有路径。 Z,你可以运行任何树遍历算法。

当然,当您构建树时,您必须确保不要引入循环。

答案 3 :(得分:1)

我会递归地继续进行,我将在所有节点对之间建立所有可能路径的列表。

我首先要为所有对(X,Y)构建列表L_2(X,Y),它是从X到Y的长度为2的路径列表;这是很容易构建的,因为这是你给出的输入列表。

然后我将使用已知列表L_2(X,Z)和L_2(Z,Y)递归地构建列表L_3(X,Y),循环遍历Z.例如,对于(C,Q),你必须尝试L_2(C,Z)和L_2(Z,Q)中的所有Z,在这种情况下,Z只能是R,你得到L_3(C,Q)= {C - > R - &gt; Q}。对于其他对,您可能有一个空的L_3(X,Y),或者从X到Y可能有许多长度为3的路径。 但是在这里构建路径时必须小心,因为它们中的一些必须被拒绝,因为它们有循环。如果路径具有相同节点的两倍,则拒绝该路径。

然后通过组合所有路径L_2(X,Z)和L_3(Z,Y)为所有对构建L_4(X,Y),同时循环遍历Z的所有可能值。您仍然删除具有循环的路径。

等等......直到你到达L_17576(X,Y)。

对此方法的一个担心是您可能会耗尽内存来存储这些列表。但请注意,在计算了L_4之后,你可以摆脱L_3等。当然你不想删除L_3(A,Z),因为这些路径是从A到Z的有效路径。

实现细节:你可以将L_3(X,Y)放在17576 x 17576数组中,其中(X,Y)处的元素是一些存储(X,Y)之间所有路径的结构。但是,如果大多数元素为空(没有路径),则可以使用HashMap<Pair, Set<Path>>,其中Pair只是存储(X,Y)的对象。我不清楚L_3(X,Y)的大部分元素是否为空,如果是,则L_4334(X,Y)的情况也是如此。

感谢@Lie Ryan在mathoverflow上指出这个相同的question。我的解决方案基本上是MRA的解决方案; Huang声称它无效,但是通过删除具有重复节点的路径,我认为我的解决方案很好。

我想我的解决方案比蛮力方法需要更少的计算,但它需要更多的内存。这么多,以至于我甚至不确定它是否可以在具有合理内存量的计算机上使用。