【问题标题】:Enumerate all paths in a weighted graph from A to B where path length is between C1 and C2枚举加权图中从 A 到 B 的所有路径,其中路径长度在 C1 和 C2 之间
【发布时间】:2011-02-20 23:22:23
【问题描述】:

给定加权图中的两个点 A 和 B,找出从 A 到 B 的所有路径,其中路径的长度在 C1 和 C2 之间。

理想情况下,每个顶点应该只被访问一次,尽管这不是硬性要求。我想我可以使用启发式方法对算法的结果进行排序,以清除“愚蠢”的路径(例如,一遍又一遍地访问相同的两个节点的路径)

我可以想到简单的蛮力算法,但有没有更复杂的算法可以提高效率?我可以想象随着图表的增长,这可能会变得昂贵。

在我正在开发的应用程序中,A 和 B 实际上是同一点(即路径必须返回起点),如果这有什么不同的话。

请注意,这是一个工程问题,而不是计算机科学问题,因此我可以使用快速但不一定 100% 准确的算法。即,如果它返回 most 的可能路径,或者如果返回的 most 路径在给定的长度范围内,则可以。

[更新]
这就是我到目前为止所拥有的。我在一个小图上工作(30 个节点,大约 100 条边)。所需时间

我正在使用有向图。
我对所有可能的路径进行深度优先搜索。

  • 在每个新节点
    • 对于离开节点的每条边
      • 如果我们的路径已经包含这条边,则拒绝这条边(换句话说,永远不要在同一方向上沿着同一条边走两次)
      • 如果边返回到我们刚刚来自的节点,则拒绝该边(换句话说,永远不要重复返回。这会删除很多“愚蠢”的路径)
      • 如果(从边缘的末端节点到目标节点 B 的最小距离 + 到目前为止行进的距离)> 最大路径长度 (C2),则拒绝边缘
      • 如果边的结束节点是我们的目标节点B:
        • 如果路径符合长度标准,请将其添加到合适的路径列表中。
        • 否则拒绝这条边(换句话说,我们只访问路径末端的目标节点 B。它不会是路径上的中间点)
      • 否则,将边添加到我们的路径并递归到它的目标节点

我使用 Dijkstra 预先计算所有节点到目标节点的最小距离。

【问题讨论】:

  • 你打算用这些路径做什么?请记住,在给定的长度范围内,可能(即使您将自己限制在简单的路径上)有指数数量的路径。
  • 我想生成长度在一定范围内的随机路径。

标签: algorithm graph


【解决方案1】:

我编写了一些 java 代码来测试我建议的 DFS 方法:该代码不检查范围内的路径,而是打印所有路径。修改代码以仅将其保持在范围内应该很简单。我还进行了一些简单的测试。它似乎给出了 10 个顶点和 50 条左右的正确结果,尽管我没有时间进行任何彻底的测试。我还运行了 100 个顶点和 1000 个边。它不会耗尽内存并继续打印新路径,直到我杀死它,其中有很多。这对于随机生成的密集图并不奇怪,但对于现实世界的图可能并非如此,例如顶点度数遵循幂律(特别是权重范围较窄。另外,如果您只是对路径长度的分布方式感兴趣)在一个范围内,一旦你产生了一定的数字,你就可以停止。

程序输出以下内容: a) 随机生成图的邻接表。 b) 到目前为止它找到的所有路径的集合。

public class AllPaths {

    int numOfVertices;
    int[] status;
    AllPaths(int numOfVertices){
        this.numOfVertices = numOfVertices;
        status = new int[numOfVertices+1];
    }

    HashMap<Integer,ArrayList<Integer>>adjList = new HashMap<Integer,ArrayList<Integer>>(); 
    class FoundSubpath{
          int pathWeight=0;
          int[] vertices;

        }


    // For each vertex, a a list of all subpaths of length less than UB found.
    HashMap<Integer,ArrayList<FoundSubpath>>allSubpathsFromGivenVertex = new HashMap<Integer,ArrayList<FoundSubpath>>();

    public void printInputGraph(){

        System.out.println("Random Graph Adjacency List:");

        for(int i=1;i<=numOfVertices;i++){
            ArrayList<Integer>toVtcs = adjList.get(new Integer(i));
            System.out.print(i+ " ");
            if(toVtcs==null){
                continue;
            }
            for(int j=0;j<toVtcs.size();j++){
                System.out.print(toVtcs.get(j)+ " ");
            }
            System.out.println(" ");
        }

    }

    public void randomlyGenerateGraph(int numOfTrials){

        Random rnd = new Random();

        for(int i=1;i < numOfTrials;i++){
            Integer fromVtx = new Integer(rnd.nextInt(numOfVertices)+1);
            Integer toVtx = new Integer(rnd.nextInt(numOfVertices)+1);
            if(fromVtx.equals(toVtx)){
                continue;
            }
            ArrayList<Integer>toVtcs = adjList.get(fromVtx);
            boolean alreadyAdded = false;
            if(toVtcs==null){
                toVtcs = new ArrayList<Integer>();
            }else{
                for(int j=0;j<toVtcs.size();j++){
                    if(toVtcs.get(j).equals(toVtx)){
                        alreadyAdded = true;
                        break;
                    }
                }
            }
            if(!alreadyAdded){
            toVtcs.add(toVtx);
            adjList.put(fromVtx, toVtcs);
            }
        }

    }

    public void addAllViableSubpathsToMap(ArrayList<Integer>VerticesTillNowInPath){
        FoundSubpath foundSpObj;
        ArrayList<FoundSubpath>foundPathsList;
        for(int i=0;i<VerticesTillNowInPath.size()-1;i++){
                Integer startVtx = VerticesTillNowInPath.get(i);
            if(allSubpathsFromGivenVertex.containsKey(startVtx)){
                foundPathsList = allSubpathsFromGivenVertex.get(startVtx);
            }else{
                foundPathsList = new ArrayList<FoundSubpath>(); 
            }

            foundSpObj = new FoundSubpath(); 
            foundSpObj.vertices = new int[VerticesTillNowInPath.size()-i-1];
            int cntr = 0;
            for(int j=i+1;j<VerticesTillNowInPath.size();j++){
                foundSpObj.vertices[cntr++] = VerticesTillNowInPath.get(j);
            }
            foundPathsList.add(foundSpObj);
            allSubpathsFromGivenVertex.put(startVtx,foundPathsList);
        }

    }

    public void printViablePaths(Integer v,ArrayList<Integer>VerticesTillNowInPath){

        ArrayList<FoundSubpath>foundPathsList;
        foundPathsList = allSubpathsFromGivenVertex.get(v);

        if(foundPathsList==null){
            return;
        }

            for(int j=0;j<foundPathsList.size();j++){
                for(int i=0;i<VerticesTillNowInPath.size();i++){
                    System.out.print(VerticesTillNowInPath.get(i)+ " ");
                }
                FoundSubpath fpObj = foundPathsList.get(j) ;
                for(int k=0;k<fpObj.vertices.length;k++){
                    System.out.print(fpObj.vertices[k]+" ");
                }
                System.out.println("");
            }
    }

    boolean DfsModified(Integer v,ArrayList<Integer>VerticesTillNowInPath,Integer source,Integer dest){


        if(v.equals(dest)){
          addAllViableSubpathsToMap(VerticesTillNowInPath);
          status[v] = 2;
          return true;
        }

        // If vertex v is already explored till destination, just print all subpaths that meet criteria, using hashmap.
        if(status[v] == 1 || status[v] == 2){
          printViablePaths(v,VerticesTillNowInPath);
          }

        // Vertex in current path. Return to avoid cycle.
        if(status[v]==1){
          return false;
        }

        if(status[v]==2){
              return true;
            }

        status[v] = 1;
        boolean completed = true;

        ArrayList<Integer>toVtcs = adjList.get(v);

        if(toVtcs==null){
            status[v] = 2;
            return true;
        }

        for(int i=0;i<toVtcs.size();i++){

          Integer vDest = toVtcs.get(i);

           VerticesTillNowInPath.add(vDest);

           boolean explorationComplete =  DfsModified(vDest,VerticesTillNowInPath,source,dest);

           if(explorationComplete==false){
           completed = false;
           }

           VerticesTillNowInPath.remove(VerticesTillNowInPath.size()-1);

        }

        if(completed){
            status[v] = 2;
        }else{
            status[v] = 0;
        }

        return completed;

    }


}


public class AllPathsCaller {

    public static void main(String[] args){

        int numOfVertices = 20;
        /* This is the number of attempts made to create an edge. The edge is usually created but may not be ( eg, if an edge already exists between randomly attempted source and destination.*/
        int numOfEdges = 200;
        int src = 1;
        int dest = 10;
        AllPaths allPaths = new AllPaths(numOfVertices);

        allPaths.randomlyGenerateGraph(numOfEdges);
        allPaths.printInputGraph();

        ArrayList<Integer>VerticesTillNowInPath = new ArrayList<Integer>();
        VerticesTillNowInPath.add(new Integer(src));
        System.out.println("List of Paths");
        allPaths.DfsModified(new Integer(src),VerticesTillNowInPath,new Integer(src),new Integer(dest));

        System.out.println("done");




    }



}

我认为您在 BFS 方面走在了正确的轨道上。对于使用 BFS 的建议解决方案,我想出了一些粗略的类似 java 的伪代码。这个想法是存储在先前遍历期间找到的子路径及其长度,以供重用。当我找到时间时,我会在今天某个时候尝试改进代码,但希望它可以为我的目标提供线索。我猜,复杂度应该是 O(E) 阶。

,


更多内容:

这似乎是一种合理的方法,尽管我不确定我是否完全理解。我已经构建了一个简单的例子来确保我这样做。让我们考虑一个所有边权重为 1 的简单图,邻接表表示如下:

A->B,C

B->C

C->D,F

F->D

假设我们想要查找从 A 到 F 的所有路径,而不仅仅是范围内的路径,并且源顶点的目标顶点按字母顺序进行探索。那么算法将如下工作:

首先从 B 开始: ABCDF ABCF

然后从 C 开始: ACDF ACF

对吗?

在这种情况下,一个简单的改进是为每个访问的顶点存储第一次访问该节点后找到的路径。例如,在这个例子中,一旦你从 B 访问 C,你会发现从 C 到 F 有两条路径:CF 和 CDF。您可以保存这些信息,在下一次迭代中,一旦您到达 C,您只需将 CF 和 CDF 附加到您找到的路径,无需进一步探索。

要查找范围内的边,您可以使用您已经为上述生成的路径描述的条件。

进一步思考:也许您根本不需要运行 Dijkstra 来找到最短路径。第一次遍历子路径时将找到子路径的长度。所以,在这个例子中,你第一次通过 B 访问 C 时的 CDF 和 CF 的长度。这个信息可以用于在下次通过 A 直接访问 C 时剪枝。这个长度会比 Dijkstra 找到的更准确,因为这将是确切的值,而不是下限。


更多内容: 该算法可能可以通过一些想法来改进。例如,每次在 Dijkstra 算法中执行松弛步骤时(wikipedia description 中的步骤 16-19),如果旧路径是合理的候选路径(小于上限)。最后,应该可以重建所有被拒绝的路径,并保持在范围内。

这个算法应该是O(V^2)。


我认为只访问每个顶点一次可能过于乐观:诸如 Djikstra 的最短路径之类的算法对于查找单个路径(最短路径)具有 v^2 的复杂度。查找所有路径(包括最短路径)是一个更难的问题,因此复杂度至少应为 V^2。

我解决这个问题的第一个想法是 Djikstra 的最短路径算法的变体。应用这个算法一次会给你最短路径的长度。这为您提供了两个顶点之间的路径长度的下限。从这条最短路径中一次删除一条边,然后重新计算最短路径应该会给你稍微长一点的路径。

反过来,可以从这些稍长的路径中移除边缘以生成更多路径,等等。一旦您有足够数量的路径,或者您生成的路径超出您的上限,您就可以停止。

这是我的第一个猜测。我是 stackoverflow 的新手:欢迎任何反馈。

【讨论】:

  • 这是我没有考虑过的方法。我理解这背后的逻辑。我想知道它是否比其他方法便宜? (我不知道)
  • 谢谢 awmross。我对这个问题添加了更多想法。
  • BTW awmross,你在考虑什么蛮力方法?我认为这是一个有趣的问题,但感觉可能很难找到有效的方法。
  • @sbkt 我试图在一个有 100 条边的图上运行我的算法,结果花了很长时间并且内存不足。我认为我的算法的复杂性不是很好。顺便说一句,与算法其余部分的运行时间相比,使用 Dijkstra 计算最短路径的时间很短。
猜你喜欢
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多