广度优先搜索算法(BFS)的原理

时间:2021-05-04 01:17:34

广度优先搜索(Breadth-First Search,简称BFS)是一种基础的图搜索算法,可以用于解决很多图论问题,如最短路径、连通性问题等。它的思想是从起始点开始,依次访问其所有直接相邻的节点,然后再依次访问这些相邻节点的相邻节点,直到找到目标节点或者搜索完整个图。

BFS一般采用队列来实现,从起始节点开始将其放入队列中,然后循环从队列中取出一个节点,遍历该节点的所有相邻节点,将未访问的相邻节点加入队列中。这样,每次取出的节点都是按照距离从起点越来越远的顺序进行访问,直到找到目标节点或者队列为空。

BFS算法的时间复杂度为O(N+M),其中N为图中的节点数,M为图中的边数。因为BFS遍历了所有的边和节点,所以它的时间复杂度与图的规模有关。在实际应用中,BFS的空间复杂度也比较高,因为需要保存已经访问过的节点和队列中的节点。

下面是一个基于BFS算法求解最短路径的示例:

假设有一张无向图如下所示:

     1
   /   \
  2     3
 / \   / \
4   5 6   7

图中有7个节点,从1到7的最短路径为1->3->7,路径长度为2。下面是BFS算法求解最短路径的Python代码实现:

from collections import deque

def bfs(graph, start, end):
    queue = deque()  # 创建队列
    queue.append(start)  # 将起始节点放入队列中
    visited = set()  # 创建已访问集合
    visited.add(start)  # 将起始节点标记为已访问
    distance = {start: 0}  # 创建距离字典,记录每个节点到起始节点的距离
    while queue:  # 当队列非空时
        node = queue.popleft()  # 取出队列中的第一个节点
        if node == end:  # 如果找到了目标节点
            return distance[end]  # 返回起始节点到目标节点的距离
        for neighbor in graph[node]:  # 遍历当前节点的所有相邻节点
            if neighbor not in visited:  # 如果相邻节点未被访问过
                visited.add(neighbor)  # 标记相邻节点为已访问
                distance[neighbor] = distance[node] + 1  # 计算相邻节点到起始节点的距离
                queue.append(neighbor)  # 将相邻节点加入队列中
from collections import deque

我们首先导入了Python标准库中的deque模块,用于创建双端队列。由于BFS算法需要使用队列来保存待访问节点,所以我们选择了使用双端队列来实现队列的功能。

def bfs(graph, start, end):

接下来定义了一个名为bfs的函数,该函数接受三个参数:graph表示待搜索的图,start表示搜索的起始节点,end表示搜索的目标节点。在函数体内我们将会使用这些参数来实现BFS算法。

queue = deque()
queue.append(start)
visited = set()
visited.add(start)
distance = {start: 0}

在函数体内,我们首先创建了一个空的双端队列queue,并将起始节点start加入队列中。同时,我们创建了一个空的集合visited,用于保存已经访问过的节点。因为起始节点是第一个被访问的节点,所以我们将其标记为已访问。

另外,我们创建了一个字典distance,用于保存每个节点到起始节点的距离。由于起始节点到自身的距离为0,所以我们将distance[start]的值设为0。

while queue:
    node = queue.popleft()
    if node == end:
        return distance[end]
    for neighbor in graph[node]:
        if neighbor not in visited:
            visited.add(neighbor)
            distance[neighbor] = distance[node] + 1
            queue.append(neighbor)

在BFS算法的主循环中,我们首先使用while循环检查队列queue是否为空。如果队列为空,说明已经遍历完了整个连通图,BFS算法结束。

接下来,我们从队列的左侧(即队头)取出一个节点node,如果该节点就是我们要找的目标节点end,那么我们就返回distance[end],表示起始节点到目标节点的距离。

如果node不是目标节点,那么我们遍历它的所有相邻节点neighbor。如果neighbor未被访问过,我们将其标记为已访问,并计算它到起始节点的距离distance[neighbor]。由于neighbor是从node扩展出来的,所以它到起始节点的距离应该为distance[node]+1。最后,我们将neighbor加入队列queue中,以便在之后的搜索中访问它的相邻节点。

这样,每次从队列中取出的节点都是按照距离从起点越来越远的顺序进行访问,直到找到目标节点或者队列为空。当整个连通图都被遍历完后,我们就可以得到起始节点到目标节点的最短距离。

例如,我们可以使用以下的图来测试上述的BFS算法:

graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

该图包含6个节点A、B、C、D、E、F和7条边,其中每个节点的相邻节点都以列表的形式保存在字典graph中。我们可以使用上述的bfs函数来计算起始节点A到目标节点F的最短距离:

>>> bfs(graph, 'A', 'F')
2

可以发现,起始节点A到目标节点F的最短距离为2,符合我们预期的结果。

在实际应用中,BFS算法经常被用于计算起点到其他节点的最短路径、网络分析、迷宫问题等等。掌握BFS算法可以帮助我们更好地理解图论和图算法,并解决一些实际问题。