简体   繁体   English

未加权图的最短路径(最少节点)

[英]Shortest path (fewest nodes) for unweighted graph

I'm trying build a method which returns the shortest path from one node to another in an unweighted graph. 我正在尝试构建一个方法,在未加权的图形中返回从一个节点到另一个节点的最短路径。 I considered the use of Dijkstra's but this seems a bit overkill since I only want one pair. 我考虑过使用Dijkstra,但这似乎有点矫枉过正,因为我只需要一对。 Instead I have implemented a breadth-first search, but the trouble is that my returning list contains some of the nodes that I don't want - how can I modify my code to achieve my goal? 相反,我已经实现了广度优先搜索,但问题是我的返回列表包含一些我不想要的节点 - 如何修改我的代码以实现我的目标?

public List<Node> getDirections(Node start, Node finish){
    List<Node> directions = new LinkedList<Node>();
    Queue<Node> q = new LinkedList<Node>();
    Node current = start;
    q.add(current);
    while(!q.isEmpty()){
        current = q.remove();
        directions.add(current);
        if (current.equals(finish)){
            break;
        }else{
            for(Node node : current.getOutNodes()){
                if(!q.contains(node)){
                    q.add(node);
                }
            }
        }
    }
    if (!current.equals(finish)){
        System.out.println("can't reach destination");
    }
    return directions;
}

Actually your code will not finish in cyclic graphs, consider graph 1 -> 2 -> 1. You must have some array where you can flag which node's you've visited already. 实际上你的代码不会在循环图中完成,考虑图1 - > 2 - > 1.你必须有一个数组,你可以标记你已经访问过哪个节点。 And also for each node you can save previous nodes, from which you came. 并且对于每个节点,您可以保存您来自的先前节点。 So here is correct code: 所以这里是正确的代码:

private Map<Node, Boolean>> vis = new HashMap<Node, Boolean>();

private Map<Node, Node> prev = new HashMap<Node, Node>();

public List getDirections(Node start, Node finish){
    List directions = new LinkedList();
    Queue q = new LinkedList();
    Node current = start;
    q.add(current);
    vis.put(current, true);
    while(!q.isEmpty()){
        current = q.remove();
        if (current.equals(finish)){
            break;
        }else{
            for(Node node : current.getOutNodes()){
                if(!vis.contains(node)){
                    q.add(node);
                    vis.put(node, true);
                    prev.put(node, current);
                }
            }
        }
    }
    if (!current.equals(finish)){
        System.out.println("can't reach destination");
    }
    for(Node node = finish; node != null; node = prev.get(node)) {
        directions.add(node);
    }
    directions.reverse();
    return directions;
}

Thank you Giolekva! 谢谢Giolekva!

I rewrote it, refactoring some: 我重写了它,重构了一些:

  • The collection of visited nodes doesn't have to be a map. 访问节点的集合不必是地图。
  • For path reconstruction, the next node could be looked up, instead of the previous node, eliminating the need for reversing the directions. 对于路径重建,可以查找下一个节点,而不是前一个节点,从而无需反转方向。
public List<Node> getDirections(Node sourceNode, Node destinationNode) {
    //Initialization.
    Map<Node, Node> nextNodeMap = new HashMap<Node, Node>();
    Node currentNode = sourceNode;

    //Queue
    Queue<Node> queue = new LinkedList<Node>();
    queue.add(currentNode);

    /*
     * The set of visited nodes doesn't have to be a Map, and, since order
     * is not important, an ordered collection is not needed. HashSet is 
     * fast for add and lookup, if configured properly.
     */
    Set<Node> visitedNodes = new HashSet<Node>();
    visitedNodes.add(currentNode);

    //Search.
    while (!queue.isEmpty()) {
        currentNode = queue.remove();
        if (currentNode.equals(destinationNode)) {
            break;
        } else {
            for (Node nextNode : getChildNodes(currentNode)) {
                if (!visitedNodes.contains(nextNode)) {
                    queue.add(nextNode);
                    visitedNodes.add(nextNode);

                    //Look up of next node instead of previous.
                    nextNodeMap.put(currentNode, nextNode);
                }
            }
        }
    }

    //If all nodes are explored and the destination node hasn't been found.
    if (!currentNode.equals(destinationNode)) {
        throw new RuntimeException("No feasible path.");
    }

    //Reconstruct path. No need to reverse.
    List<Node> directions = new LinkedList<Node>();
    for (Node node = sourceNode; node != null; node = nextNodeMap.get(node)) {
        directions.add(node);
    }

    return directions;
}

It is really no simpler to get the answer for just one pair than for all the pairs. 得到一对的答案比对所有对都简单得多。 The usual way to calculate a shortest path is to start like you do, but make a note whenever you encounter a new node and record the previous node on the path. 计算最短路径的常用方法是像您一样开始,但只要遇到新节点并在路径上记录上一个节点就做一个注释。 Then, when you reach the target node, you can follow the backlinks to the source and get the path. 然后,当您到达目标节点时,您可以跟踪到源的反向链接并获取路径。 So, remove the directions.add(current) from the loop, and add code something like the following 因此,从循环中删除directions.add(current) ,并添加类似以下内容的代码

Map<Node,Node> backlinks = new HashMap<Node,Node>();

in the beginning and then in the loop 在开始然后在循环中

if (!backlinks.containsKey(node)) {
    backlinks.add(node, current);
    q.add(node);
}

and then in the end, just construct the directions list in backwards using the backlinks map. 然后最后,使用backlinks映射向后构建directions列表。

Every time through your loop, you call 每次通过循环,你都会打电话

directions.Add(current);

Instead, you should move that to a place where you really know you want that entry. 相反,你应该把它移到你真正知道你想要那个条目的地方。

You must include the parent node to each node when you put them on your queue. 将它们放入队列时,必须将父节点包含在每个节点中。 Then you can just recursively read the path from that list. 然后,您可以递归地从该列表中读取路径。

Say you want to find the shortest path from A to D in this Graph: 假设您要在此图中找到从A到D的最短路径:

     /B------C------D
   /                |
 A                 /
   \             /
     \E---------

Each time you enqueue a node, keep track of the way you got here. 每次排队节点时,都要跟踪到达此处的方式。 So in step 1 B(A) E(A) is put on the queue. 因此,在步骤1 B(A)中,E(A)被放在队列中。 In step two B gets dequeued and C(B) is put on the queue etc. Its then easy to find your way back again, by just recursing "backwards". 在第二步中B出队并且C(B)被放入队列等。然后通过“向后”递归,它很容易找到回来的路。

Best way is probably to make an array as long as there are nodes and keep the links there, (which is whats usually done in ie. Dijkstra's). 最好的方法可能是制作一个数组,只要有节点并保持链接在那里(这通常是在Dijkstra's中完成的)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM