【问题标题】:Shortest path (fewest nodes) for unweighted graph未加权图的最短路径(最少节点)
【发布时间】:2010-11-15 22:11:51
【问题描述】:

我正在尝试构建一种方法,该方法在未加权图中返回从一个节点到另一个节点的最短路径。我考虑过使用 Dijkstra 的,但这似乎有点矫枉过正,因为我只想要一对。相反,我实现了广度优先搜索,但问题是我的返回列表包含一些我不想要的节点 - 如何修改我的代码以实现我的目标?

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;
}

【问题讨论】:

  • 你为什么不想要其中的一些节点?
  • 并不是所有的都属于一条最短路径路由
  • 类Node是否正确覆盖equals和hashcode?​​span>
  • 当我在 2D 网格上做这样的事情时,我发现 A*(A 星)算法更容易理解。
  • 我认为使用 Dijkstra 的算法是最简单的方法。您在节点上进行一次传递以标记它们的成本,并在另一次传递以选择任何最短路径。看起来您正在尝试一次性完成所有操作,我认为使用此算法不容易完成。

标签: java algorithm graph shortest-path breadth-first-search


【解决方案1】:

实际上,您的代码不会在循环图中完成,请考虑图 1 -> 2 -> 1。您必须有一些数组,您可以在其中标记您已经访问过的节点。而且对于每个节点,您都可以保存您来自的先前节点。所以这里是正确的代码:

private Map> vis = new HashMap(); private Map prev = new HashMap(); public List getDirections(节点开始,节点结束){ 列表方向 = new LinkedList(); 队列 q = new LinkedList(); 节点电流 = 开始; q.add(当前); vis.put(当前,真); 而(!q.isEmpty()){ 当前 = q.remove(); if (current.equals(finish)){ 休息; }别的{ for(节点节点:current.getOutNodes()){ 如果(!vis.contains(节点)){ q.add(节点); vis.put(节点,真); prev.put(节点,当前); } } } } if (!current.equals(finish)){ System.out.println("无法到达目的地"); } for(节点节点=完成;节点!=空;节点= prev.get(节点)){ 方向。添加(节点); } 方向.reverse(); 返回方向; }

【讨论】:

  • 你的意思当然是 vis.get(node) == null 否则会出现空指针异常
  • 是的,我已经用 contains 方法改变了它。我在这里写的代码没有任何 IDE,所以可能有一些拼写错误:)
  • 这是一个很好的例子 - 最后它为我点击了如何找到最短路径以及步骤
  • 我认为这不适用于下图:A->C->D->F A->D->F 最短路径是最后一条,但 prev[D] 将是在到达 F 之前被 C(而不是 A)覆盖。所以在返回的路径(方向)处将是:A->C->D->F
【解决方案2】:

谢谢 Giolekva!

我重写了,重构了一些:

  • 访问节点的集合不一定是地图。
  • 对于路径重建,可以查找下一个节点,而不是前一个节点,从而无需反转方向。
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;
}

【讨论】:

  • 这个方法是错误的,因为下一个例子的结果会很糟糕。对于接下来的对 1-2 1-3 2-5 getDirections("1","5") = "1","3"'
【解决方案3】:

仅仅得到一对的答案并不比得到所有的对更简单。计算最短路径的常用方法是像你一样开始,但每当遇到新节点时记下并记录路径上的前一个节点。然后,当您到达目标节点时,您可以按照反向链接到源并获取路径。因此,从循环中删除 directions.add(current),并添加如下代码

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

在开始,然后在循环中

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

最后,只需使用backlinks 映射向后构造directions 列表。

【讨论】:

    【解决方案4】:

    当您将它们放入队列时,您必须将父节点包含到每个节点中。然后你可以从该列表中递归读取路径。

    假设你想在这个图中找到从 A 到 D 的最短路径:

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

    每次将节点排入队列时,请跟踪您到达此处的方式。 所以在步骤 1 B(A) E(A) 被放入队列。在第二步中,B 出队,C(B) 被放入队列等。然后很容易再次找到返回的路,只需递归“向后”。

    最好的方法可能是创建一个数组,只要有节点并将链接保留在那里,(这通常在 Dijkstra 中完成)。

    【讨论】:

      【解决方案5】:

      每次通过你的循环,你调用

      directions.Add(current);
      

      相反,您应该将其移至您真正知道需要该条目的位置。

      【讨论】:

      • 怎么知道加不合适?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-19
      • 1970-01-01
      • 2022-01-22
      • 2018-07-16
      • 1970-01-01
      相关资源
      最近更新 更多