【算法】图的 深度优先搜索 广度优先搜索 复杂度分析 python代码实现

深度优先搜索算法和广度优先搜索算法都是基于“”这种数据结构的。

作为图的搜索算法,既可用于有向图,也可用于无向图,以下均用无向图讲解

广度优先搜索

Breadth-First-Search,BFS
一种“地毯式”层层推进的搜索策略,先查找离起始顶点最近的,然后是次近的,依次往外搜索。

s 表示起始顶点,t 表示终止顶点。搜索一条从 s 到 t 的路径。
实际上,求得的路径就是从 s 到 t 的最短路径
广度优先搜索
代码(下面有完整代码)中包含3个重要的辅助变量:

  • visited -> list,保存已经访问的顶点,避免重复访问;
  • q -> queue,队列,逐层访问。
    每次弹出第k层顶点时,将第与k连接的k+1层顶点添加至队尾(添加前会通过visited检测是否已经访问,只入队未访问过的顶点);
  • prev -> list,保存搜索路径,例如prev[3] = 2,代表顶点3的前驱顶点为顶点2,用于后期打印。

在这里插入图片描述在这里插入图片描述
时间复杂度O(n),空间复杂度O(n)。

最坏的情况,s 与 t 相距较远,需要遍历完整的图才能找到,而遍历是不重复的,最多遍历全部顶点,所以复杂度是O(n)。
中间的三个辅助变量,大小均不会超过顶点个数,所以空间复杂度也是O(n)。

深度优先搜索

Depth-First-Search,DFS
用“走迷宫”举例,随意选择一个岔路口来走,走着走着发现走不通,你就回退到上一个岔路口,重新选择一条路继续走,直到最终找到出口。 这种走法就是一种深度优先搜索策略。
用的是回溯思想,后面会单独讲回溯。

深度优先搜索找出的路径并不是最短路径
深度优先搜索
代码中用到的辅助变量:

  • visited
  • prev
  • found -> bool,标识作用,已经找到终止顶点 t 后,不再递归查找。

前两个变量与上面的广度优先遍历一致。

时间复杂度O(n),空间复杂度O(n)。
每条边最多会被访问两次,所以时间复杂度为O(n)。
空间复杂度原因与广度优先搜索一致。

代码

from collections import deque
class Graph:
    """无向图"""
    def __init__(self, num_vertices):
        # 顶点数量
        self._num_vertices = num_vertices
        # [[], [], [] ...],内部每个小列表存储该顶点的相连的其他顶点
        self._adjacency = [[] for _ in range(num_vertices)]
    def add_edge(self, s, t):
        """无向图一条边存两次"""
        self._adjacency[s].append(t)
        self._adjacency[t].append(s)
    def _generate_path(self, s, t, prev):
        # 用or会打印第一个顶点,用and则不会
        if prev[t] or s != t:
            yield from self._generate_path(s, prev[t], prev)
        yield str(t)
        
    def bfs(self, s, t):
        """通过广度优先搜索,打印s -> t的路径"""
        if s == t:
            return
        # 定义文中提到的的三个重要变量,并将第一个顶点s加入各变量中
        visited = [False] * self._num_vertices
        visited[s] = True
        q = deque()
        q.append(s)
        prev = [None] * self._num_vertices
        
        while q:
            v = q.popleft()
            # 遍历顶点v相连的其他顶点,并将每个顶点加入到队列中继续遍历
            for neighbour in self._adjacency[v]:
                # 各顶点未被遍历
                if not visited[neighbour]:
                    # 记录path
                    prev[neighbour] = v
                    # 符合终止条件则打印
                    if neighbour == t:
                        print(' -> '.join(self._generate_path(s, t, prev)))
                        return
                    # 记录遍历过的顶点
                    visited[neighbour] = True
                    # 将加入队列,后面会遍历该顶点
                    q.append(neighbour)
        
    def dfs(self, s, t):
        found = False
        visited = [False] * self._num_vertices
        prev = [None] * self._num_vertices
        # 递归
        def _dfs(from_vertex):
            nonlocal found
            if found:
                return
            visited[from_vertex] = True
            if from_vertex == t:
                found = True
                return
            for neighbour in self._adjacency[from_vertex]:
                if not visited[neighbour]:
                    prev[neighbour] = from_vertex
                    _dfs(neighbour)
        _dfs(s)
        print(' -> '.join(self._generate_path(s, t, prev)))


if __name__ == "__main__":
    """
      0 ----- 1 ----- 2
      |     / |     / |
      |    /  |    /  |
      |   /   |   /   |
      |  /    |  /    |
      | /     | /     |
      3 ----- 4 ----- 5
              |     / |
              |    /  |
              |   /   |
              |  /    |
              | /     |
              6 ----- 7
    """
    graph = Graph(8)
    graph.add_edge(0, 1)
    graph.add_edge(0, 3)
    graph.add_edge(1, 2)
    graph.add_edge(1, 4)
    graph.add_edge(2, 5)
    graph.add_edge(3, 4)
    graph.add_edge(4, 5)
    graph.add_edge(4, 6)
    graph.add_edge(5, 7)
    graph.add_edge(6, 7)

    print(graph._adjacency)
    # 输出结果:[[1, 3], [0, 2, 4], [1, 5], [0, 4], [1, 3, 5, 6], [2, 4, 7], [4, 7], [5, 6]]
    graph.bfs(0, 7)
    # 输出结果:0 -> 1 -> 2 -> 5 -> 7
    graph.dfs(0, 7)
    # 输出结果:0 -> 1 -> 2 -> 5 -> 4 -> 6 -> 7 

本文是极客时间王争 数据结构与算法 课程的笔记,推荐此课,喜欢可以购买课程。
代码引用自 https://github.com/wangzheng0822/algo/blob/master/python/31_bfs_dfs/bfs_dfs.py ,根据代码作了注释和部分优化。

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值