我编写了一些 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 的新手:欢迎任何反馈。