Python广度优先查找和深度优先查找(2)

作者: 霡霂976447044 | 来源:发表于2019-03-24 13:56 被阅读3次

图论,特别是图的ADT(抽象数据类型)在数学和计算机科学中使用是非常广泛的。图由顶点(节点)和边(可能是有方向的/加权重的)组成,边连接顶点,这种数据结构可以有效的解决一些领域的问题。算法设计里面,最常用的就是用来解决两个顶点之间是否存在一条路径可以到达,或者说,是否存在。图的边权重和方向都可能是算法设计者要考虑的。在这篇文章中,我将会探索两个简单的算法,深度优先查找和广度优先查找,要达到如下突出的目标:

  • 找到所有的顶点在所有的连通域
  • 返回在两个顶点之间所有可用的的路径
  • 在BFS的情况下,返回最短路径。(长度的计算是根据边的数量多少)

为了清楚的讨论每个算法,我创建了一个连接图,有6个顶点和6个incident边,这个图是无向的,和没有给边分配权重的。长度会基于路径之间的经过的顶点数量计算。有两个常用的方式来代表两个一个图,第一个是使用相邻矩阵(对密集图),第二个方式是使用相邻列表(对稀疏图),这里选择相邻列表实现,使用字典存储顶点,并且值为一个set类型集合用来表示该顶点相邻节点。由于图是无向的,每个边都存储在相邻集的两个入射节点中。

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

看下面这张图的刻画,你会留意到在 ‘F’ 和 ‘C/E’的相邻节点包含一个循环,这是故意包括的,以便为算法提供在两个所需节点之间返回多个路径的选项。

graph.png

Depth-First Search

第一个要讨论的算法是深度优先查找,正如名字那样蕴意着,顺着一个顶点的分支一直找下去知道回溯。这种特点,使得可以使用迭代或者递归实现这个算法,下面是大概流程:

  • 标记当前顶点为已经访问
  • 探索所有相邻节点,前提是不在已经查找的列表里面。

Connected Component

下面的实现是使用栈(FILO)的数据结构构造,函数会返回在连接域中所有可用的顶点数据。使用Python的set类型的减法操作符重载来删除set里面的数据,从而达到只添加不重复的相邻顶点。

def dfs(graph, start):
    visited, stack = set(), [start]
    while stack:
        vertex = stack.pop()
        if vertex not in visited:
            visited.add(vertex)
            stack.extend(graph[vertex] - visited)
    return visited

dfs(graph, 'A') # {'E', 'D', 'F', 'A', 'C', 'B'}

第二种实现的功能和第一个是相同的,但是,这次我们使用更简洁的递归实现。由于Python的语法,默认参数只会创建一次,我们需要在每次用户调用的时候创建一个新的数据集,另一个Python语言的内部机制是,函数的参数传递是通过引用传递,所以不用在每次递归重新创建就可以访问可变集合的数据。

def dfs(graph, start, visited=None):
    if visited is None:
        visited = set()
    visited.add(start)
    for next in graph[start] - visited:
        dfs(graph, next, visited)
    return visited

dfs(graph, 'C') # {'E', 'D', 'F', 'A', 'C', 'B'}

Paths

我们可以调整之前两个实现,来返回开始顶点到目标顶点之间的所有可能路径。下面的实现也是使用的栈(FILO)的数据结构,会使用迭代来解决。当遇到目标路径,就会使用yield。使用python的generator可以只计算你想得到的路径。

def dfs_paths(graph, start, goal):
    stack = [(start, [start])]
    while stack:
        (vertex, path) = stack.pop()
        for next in graph[vertex] - set(path):
            if next == goal:
                yield path + [next]
            else:
                stack.append((next, path + [next]))

list(dfs_paths(graph, 'A', 'F')) # [['A', 'C', 'F'], ['A', 'B', 'E', 'F']]

下面的实现是使用递归的方法,使用 ‘yield from’来返回路径。

def dfs_paths(graph, start, goal, path=None):
    if path is None:
        path = [start]
    if start == goal:
        yield path
    for next in graph[start] - set(path):
        yield from dfs_paths(graph, next, goal, path + [next])

list(dfs_paths(graph, 'C', 'F')) # [['C', 'F'], ['C', 'A', 'B', 'E', 'F']]

Breath-First Search

另一个算法叫做广度优先搜索算法,它和DFS算法返回的结果是一样的,只不过BFS会保证先返回最近的路径。BFS使用递归的话会比较棘手,所以这里只描述了迭代的版本实现。探寻每一个顶点的步骤其实和DFS实现是一样的,但是,要把栈(FILO)替换为队列(FIFO),来达到广度优先。这个步骤会保证第一个得到的路径是最短的,当在以路径经过的顶点数量为计算路径长度标准的时候。

Connected Component

和迭代版本的DFS实现类似,只需要把栈结构变为队列的结构(把pop()变为pop(0))。

def bfs(graph, start):
    visited, queue = set(), [start]
    while queue:
        vertex = queue.pop(0)
        if vertex not in visited:
            visited.add(vertex)
            queue.extend(graph[vertex] - visited)
    return visited

bfs(graph, 'A') # {'B', 'C', 'A', 'F', 'D', 'E'}

Paths

这个实现也是可以根据之前的DFS实现再次简单的更改的,栈改为队,它先返回的路径总是会最短的。

def bfs_paths(graph, start, goal):
    queue = [(start, [start])]
    while queue:
        (vertex, path) = queue.pop(0)
        for next in graph[vertex] - set(path):
            if next == goal:
                yield path + [next]
            else:
                queue.append((next, path + [next]))

list(bfs_paths(graph, 'A', 'F')) # [['A', 'C', 'F'], ['A', 'B', 'E', 'F']]

我们知道BFS生成器方法会先返回最短的路径,所以我们可以创建一个有用的函数,简单的返回最短路径,如果没找到就返回None。当我们使用生成器的时候,理论上它提供的性能是差不多的性能,就像在BFS实现中分解和返回第一个匹配路径一样。

def shortest_path(graph, start, goal):
    try:
        return next(bfs_paths(graph, start, goal))
    except StopIteration:
        return None

shortest_path(graph, 'A', 'F') # ['A', 'C', 'F']

Resources

orig:https://eddmann.com/posts/depth-first-search-and-breadth-first-search-in-python/

相关文章

  • 深度优先和广度优先查找以及拓扑排序

    深度和广度优先查找 归属:蛮力法 简称:DFS(深度优先查找)、BFS(广度优先查找) 思想:DFS: 深度优先查...

  • 前端常见面试题目(六)

    一、介绍下深度优先遍历和广度优先遍历,如何实现 通过用深度优先遍历和广度优先遍历对这个dom树进行查找来理解1、 ...

  • Python广度优先查找和深度优先查找(2)

    图论,特别是图的ADT(抽象数据类型)在数学和计算机科学中使用是非常广泛的。图由顶点(节点)和边(可能是有方向的/...

  • Python广度优先查找和深度优先查找(1)

    图,特别是图的ADT(抽象数据类型)在计算机科学和数学领域是应用非常广泛的。 图基本认识 图模拟一组连接,假如你在...

  • 2020-08-04 算法分类

    1、动态规划 2、贪心 3、查找 二分查找 二叉搜索树 深度/广度优先搜索 4、排序 写于20200804晚,晴

  • 爬虫(3-5)

    深度优先和广度优先1 网站的树结构2 深度优先算法和实现3 广度优先算法和实现 深度优先输出A,B,D,E,I,C...

  • 5. 深度优先、广度优先

    1. 二叉树的深度优先遍历和广度优先遍历2. 深度优先搜索递归和非递归实现 深度优先(DFS):前序遍历 广度优先...

  • 2018-09-26

    算法 二分查找 运行结果 选择排序 运行结果 快速排序 运行结果 广度优先搜索 运行结果 深度优先搜索 运行结果 ...

  • 广度优先查找和深度优先查找的js实现

    今天在掘金上看到一篇文章,里面给出了一个试题,用广度优先查找实现一个dom结构的查询,并输出tag和类。dom结构...

  • Python爬虫:关于 广度优先 和 深度优先

    广度优先和深度优先 关于广度优先和深度优先,首先,不管是广度还是深度,都需要定义一个爬取的深度 crawl_dee...

网友评论

    本文标题:Python广度优先查找和深度优先查找(2)

    本文链接:https://www.haomeiwen.com/subject/wnqsvqtx.html