枚举从A到B的加权图中的所有路径,其中路径长度在C1和C2之间

时间:2011-02-20 23:22:24

标签: algorithm graph

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

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

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

在我正在开发的应用程序中,A& B实际上是相同的点(即路径必须返回到起点),如果这有任何不同。

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

[UPDATE]
这就是我到目前为止所拥有的。我有一个小图表(30个节点,大约100个边缘)。所需时间是< 100ms的

我正在使用有向图 我首先深入搜索所有可能的路径。

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

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

1 个答案:

答案 0 :(得分:0)

我写了一些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-&GT; B,C

B-&以及c

C-&GT; d,F

F-&GT; d

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

首先从B开始: ABCDF ABCF

然后从C开始: ACDF ACF

这是对的吗?

在这种情况下,一个简单的改进就是为每个访问过的顶点存储第一次访问该节点后找到的路径。例如,在此示例中,一旦从B访问C,您会发现从C:CF和CDF有两条到F的路径。您可以保存此信息,在下一次迭代中,一旦达到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的新手:欢迎任何反馈。