簡體   English   中英

BFS(廣度優先搜索)——不計算Java中的最短路徑

[英]BFS (Breadth First Search) - Not calculating the shortest Path in Java

我在Java通過BFS算法計算最短路徑有問題。

當我試圖定義從節點 0 到節點 7 的路徑時,我得到了這個結果。 (0 -> 2 -> 3 -> 4 -> 5)

我該如何解決這個問題?

這是我的節點 Class

public class Node {
  private final int value;
  private final Set<Node> siblingNodes;

  public Node(int value) {
    this.value = value;
    siblingNodes = new HashSet<Node>();
  }

  public Set<Node> getSiblingNodes() {
    return siblingNodes;
  }

  public boolean equals(Node compareTo) {
    return (compareTo.getValue() == value);
  }

  public int getValue() {
    return value;
  }

  public void addSiblingNode(Node node) {
    siblingNodes.add(node); 
  }
}

這是下面顯示的代碼片段。

  public static void main(String[] args) {
    /**
     *
     *        1             -> 5
     * 0 ->           -> 4
     *        2 -> 3        -> 6    -> 7
     *
     */
        Node node0 = new Node(0);

        Node node1 = new Node(1);
        Node node2 = new Node(2);
        node0.addSiblingNode(node1);
        node0.addSiblingNode(node2);    

        Node node3 = new Node(3);
        node2.addSiblingNode(node3);

        Node node4 = new Node(4);
        node3.addSiblingNode(node4);

        Node node5 = new Node(5);
        Node node6 = new Node(6);
        node4.addSiblingNode(node5);
        node4.addSiblingNode(node6);
        
        Node node7 = new Node(7);
        node6.addSiblingNode(node7);

        List<Node> shortestPath = getDirections(node0, node7);
        for(Node node : shortestPath) {
          System.out.println(node.getValue());
        }
      }

      public static List<Node> getDirections(Node sourceNode, Node destinationNode) {
        // Initialization.
        Map<Node, Node> nextNodeMap = new HashMap<Node, Node>();
        Node currentNode = sourceNode;
        Node previousNode = 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)) {
            // Handle case where the node leading to the destinatino node
            // itself pointed to multiple nodes. In this case,
            // nextNodeMap is incorrect and we need to rely on the previously
            // seen node.
            // Also need to check for edge-case of start node == end node.
            if (!previousNode.equals(currentNode)) {
              nextNodeMap.put(previousNode, currentNode);
            }
            break;
          } else {
            for (Node nextNode : currentNode.getSiblingNodes()) {
              if (!visitedNodes.contains(nextNode)) {
                queue.add(nextNode);
                visitedNodes.add(nextNode);

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

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

最好使用Map來構建節點之間的路徑。 但問題是您正在覆蓋 Values 因為不能有超過一個具有相同 Key 的條目( 4 -> 6已被4 -> 5覆蓋,並且您收到了不正確的結果)。

為了解決這個問題,我們可以反轉 Map,即Keys表示兄弟節點(鍵是唯一的),而Values - 父節點(值可以重復)。 並且當找到destination節點(或已訪問所有節點)時,將以相反的順序恢復構建路徑,即從destinationsource (我們需要反轉列表)。

請注意,訪問節點的HashSet是冗余的,因為此信息已反映在 Map 中。

BFS 的實現可能是這樣的:

public static List<Node> getDirections(Node source, Node destination) {
    Map<Node, Node> paths = new HashMap<>(); // Map of paths also allows to track the visited nodes
    Queue<Node> queue = new ArrayDeque<>();  // is more performant than LinkedList
    queue.add(source);
    paths.put(source, null); // the starting point of the path
    
    boolean isFound = false;
    
    while (!isFound && !queue.isEmpty()) {
        Node current = queue.remove();
        
        for (Node sibling : current.getSiblingNodes()) {
            if (paths.containsKey(sibling)) continue;
            // update the Map of paths
            paths.put(sibling, current);
            // the target node was found
            if (sibling.equals(destination)) {
                isFound = true; // we need to terminate the search, no need to explore all nodes if the path is found
                break;
            }
            queue.add(sibling); // adding the sibling to the queue
        }
    }
    return getPath(source, destination, paths);
}

生成路徑的輔助方法:

public static List<Node> getPath(Node start, Node end, Map<Node, Node> paths) {
    List<Node> path = new ArrayList<>();
    Node current = end;
    path.add(current);
    while (current != start && current != null) { // if there's no path from start to end current eventually will become null
        path.add(paths.get(current));
        current = paths.get(current);
    }
    Collections.reverse(path);
    return current != null ? path : Collections.emptyList();
}

注意Node class 中的equals()實現實際上並沒有覆蓋java.lang.Object.equals()因為簽名不匹配。 此外,缺少hashCode()的實現,這違反了equals()的約定。 因此,在基於哈希的 collections 中,將使用equalshashCode的默認實現。 它並沒有在原始代碼中造成麻煩,只是因為 OP 在字面上使用相同的對象。

這是正確實施的示例:

public class Node {
    private final int value;
    private final Set<Node> siblingNodes = new HashSet<>();
    
    // constructor, getters, addSiblingNode(), etc.
    
    @Override
    public boolean equals(Object o) {
        return o instanceof Node other && value == other.value;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}

main()

public static void main(String[] args) {
    // sample data from the question
    
    List<Node> shortestPath = getDirections(node0, node7);
    
    String path = shortestPath.stream()
        .map(Node::getValue)
        .map(String::valueOf)
        .collect(Collectors.joining(" -> "));

    System.out.println(path);
}

Output:

0 -> 2 -> 3 -> 4 -> 6 -> 7

您的getDirections()實現似乎是正確的。 但是Node class 有問題,需要在Node中添加如下兩個方法。 這些是將Node對象存儲在MapSet中所必需的。

    @Override
    public int hashCode() {
        return value;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof Node n
            && n.value == value;
    }

output:

0
2
3
4
6
7

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM