[英]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
節點(或已訪問所有節點)時,將以相反的順序恢復構建路徑,即從destination
到source
(我們需要反轉列表)。
另請注意,訪問節點的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 中,將使用equals
和hashCode
的默認實現。 它並沒有在原始代碼中造成麻煩,只是因為 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
對象存儲在Map
或Set
中所必需的。
@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.