广度优先搜索
目录
一、概述
广度优先搜索是一种遍历算法,可以用于遍历树或者图。广度优先搜索(BFS)在遍历时会先遍历完同一层次或者说邻居节点,而不是像广度优先搜索一搜到底。
接下来,我们来看看这种算法在树和图的遍历中是怎么实现的。
二、树结构中的广度优先搜索
树的 BFS 算法背后的想法是维护一个节点队列,以确保遍历的顺序。在算法开始时,队列只包含根节点。只要队列仍然包含一个或多个节点,我们就会重复这些步骤:时间复杂度O(n)
- 从队列中弹出一个节点
- 如果该节点是我们正在搜索的节点,则搜索结束
- 否则,将此节点的子节点添加到队列的末尾,然后重复以上步骤
- 树结构中没有循环可以保证程序必定会终止。
public static <T> Optional<Tree<T>> search(T value, Tree<T> root) {
Queue<Tree<T>> queue = new ArrayDeque<>();
queue.add(root);
while(!queue.isEmpty()) {
Tree<T> currentNode = queue.remove(); // 从队列中弹出一个节点
if (currentNode.getValue().equals(value)) {
return Optional.of(currentNode); // 如果该节点是我们正在搜索的节点,则搜索结束
} else {
queue.addAll(currentNode.getChildren()); // 否则,将此节点的子节点添加到队列的末尾
}
}
return Optional.empty(); // 没有找到
}
public class Tree<T> { // 树结构
private T value;
private List<Tree<T>> children;
private Tree(T value) {
this.value = value;
this.children = new ArrayList<>();
}
public static <T> Tree<T> of(T value) {
return new Tree<>(value);
}
public Tree<T> addChild(T value) {
Tree<T> newChild = new Tree<>(value);
children.add(newChild);
return newChild;
}
}
三、图结构中的广度优先搜索
图和树的主要区别是,图包含环。如果我们简单地将前面的算法应用于一个有循环的图,它会一直循环。为了避免在环内循环搜索,需要在遍历节点后保留访问过的节点的集合,确保我们不会访问它们两次。
- 从队列中弹出一个节点
- 检查节点是否已经存在被访问过节点集合中,如果是则跳过它
- 如果该节点是我们正在搜索的节点,则搜索结束
- 否则,将其添加到已被访问节点集合中
- 将此节点的邻居节点添加到队列中,然后重复以上步骤
public static <T> Optional<Node<T>> search(T value, Node<T> start) {
Queue<Node<T>> queue = new ArrayDeque<>();
queue.add(start);
Node<T> currentNode;
Set<Node<T>> alreadyVisited = new HashSet<>();
while (!queue.isEmpty()) {
currentNode = queue.remove(); // 从队列中弹出一个节点
LOGGER.debug("Visited node with value: {}", currentNode.getValue());
if (currentNode.getValue().equals(value)) { // 如果该节点是我们正在搜索的节点,则搜索结束
return Optional.of(currentNode);
} else {
alreadyVisited.add(currentNode); // 将其添加到已被访问节点集合中
queue.addAll(currentNode.getNeighbors()); // 将此节点的邻居节点添加到队列中
queue.removeAll(alreadyVisited); // 检查节点是否已经存在被访问过节点集合中,如果是则跳过它,确保不会陷入死循环
}
}
return Optional.empty();
}
public class Node<T> {
private T value;
private Set<Node<T>> neighbors;
public Node(T value) {
this.value = value;
this.neighbors = new HashSet<>();
}
public void connect(Node<T> node) {
if (this == node) throw new IllegalArgumentException("Can't connect node to itself");
this.neighbors.add(node);
node.neighbors.add(this);
}
}
- 让我们考虑节点的数量n和图的连接数c。在最坏的情况下(没有找到节点),我们可能会使用addAll()和 removeAll()方法来添加和删除节点,直到达到连接数,所以这些操作是O(c)的复杂度。假设c > n,那整个算法的复杂度将为 O(c);否则,是 O(n)。这通常被称为 O(n + c),可以解释为复杂性取决于n和c之间的最大数。
四、小结
在本文中,我们讨论了树和图数据结构的广度优先搜索。
参考:
- https://www.baeldung.com/java-breadth-first-search —by baeldung