定向图遍历 - 所有路径

时间:2016-05-27 15:39:07

标签: graph traversal directed-graph

给出带有

的有向图
  • 根节点
  • 有些离开节点
  • 多个节点可以连接到同一节点
  • 可以存在周期

我们需要打印从根节点到所有叶子节点的所有路径。这是我遇到这个问题最接近的问题 Find all paths between two graph nodes

1 个答案:

答案 0 :(得分:0)

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