找到两个图节点之间的所有路径

时间:2012-03-02 15:25:10

标签: algorithm graph-theory

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

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

11 个答案:

答案 0 :(得分:48)

查找所有可能的路径是一个难题,因为存在指数的简单路径。即使找到第k个最短路径[或最长路径]也是NP-Hard

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

请注意,在每个包含周期[不是DAG]的图表中,st之间可能存在无限数量的路径。

答案 1 :(得分:10)

我已经实现了一个版本,它基本上可以找到从一个节点到另一个节点的所有可能路径,但它不计算任何可能的“周期”(我正在使用的图形是循环的)。所以基本上,没有一个节点会在同一路径中出现两次。如果图是非周期性的,那么我想你可以说它似乎找到了两个节点之间的所有可能路径。它似乎工作得很好,并且对于我的图形大小~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();
        }
    }
}

答案 2 :(得分:9)

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

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

当然,如果图表具有一些选择良好的属性,这可能很容易。我考虑的是一般情况。


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

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

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

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

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

答案 3 :(得分:4)

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

No valid MBR/FAT-BS signature found in sector 0

答案 4 :(得分:2)

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_found引用传递。很明显,递归函数不会返回任何内容。妥善处理。
  • 此处graph假设某种形式的hashed结构。有很多方法可以实现图表。无论哪种方式,graph[vertex]都会为您提供定向图表中相邻顶点的列表 - 相应调整。
  • 这假设您已经预处理以删除“扣环”(自循环),循环和多边

答案 5 :(得分:2)

以下功能(经过修改的 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)]

例如,下面的图形( DAG G

给出
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')),它将返回以下路径:

enter image description here

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

如果您真的关心从最短路径到最长路径的路径排序,那么使用修改后的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

答案 9 :(得分:0)

我想你想找到简单的&#39;路径(如果路径中没有节点出现多次,则路径很简单,除了第一个和最后一个节点)。

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

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

答案 10 :(得分:0)

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

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