Python广度优先查找和深度优先查找(1) 图基本认识 广度优先搜索(breadth-first search, BFS) 深度优先查找

图,特别是图的ADT(抽象数据类型)在计算机科学和数学领域是应用非常广泛的。

图基本认识

图模拟一组连接,假如你在A点,你到B点的路径,可以使用图来代表。


图由图由顶点(vertex,node)和边(edge)组成。一个顶点(vertex)可以与多个顶点连接,通过一条线,这条线称为边(edge)。

图用于模拟一组数据是如何连接的

在Python中使用字典来实现这种连接关系。在之前的文章,我已经介绍了Python字典的实现原理。

广度优先搜索(breadth-first search, BFS)

首先,BFS可以解决两种问题

    1. 从节点A出发,能不能到节点X?
    1. 从节点A出发,到节点X的最短路径是哪条?

如何理解广度:
假如现在你需要在你的朋友的关系网中,找到一个卖芒果的,应该如何做?下图是你的朋友关系网的情况。

广度优先,就是, 在你的关系网中,一度关系胜过二度关系。广度优先,就是先在一度关系中找到,再往外层的度找。

现在我们用BFS来解决第一个问题。

这里参考了《算法图解》的例子。

先把上图写为对应的Python代码:

graph = dict()
graph['your'] = ['alice', 'boob', 'claire']
graph['boob'] = ['anuj', 'peggy']
graph['alice'] = ['peggy']
graph['claire'] = ['thom', 'jonny']
graph['anuj'] = []
graph['peggy'] = []
graph['thom'] = []
graph['jonny'] = []

我们使用队列(FIFO)的思想,将关系网的人加入到队列,然后弹出一个元素,并且把该元素的关系网的朋友也加入到队列。下面是代码实现:

def person_is_seller(name):
    return name[-1] == 'm'


def search(graph, name):
    search_queue = deque()
    search_queue += graph[name]
    searched = []

    while search_queue:
        person = search_queue.popleft()
        if person not in search_queue:
            if person_is_seller(person):
                print(searched)
                print(person + ' is a mango seller')
                return True
            else:
                search_queue += graph[person]
                searched.append(person)
    return False

这样的代码只是告诉了我们,在关系网中能够找到那个人(以m字母结尾),并且可以得到那个人的信息。
上面的代码还是比较简单的,现在我们来解决第二个问题,最短路径是哪个?也就是,如何把走过的路径保存起来?

def bfs2(graph, start):
    queue = [(start, [start])]  #  ['your', ['your']]
    while queue:
        (vertex, path) = queue.pop(0)
        for next_friend in graph[vertex]:
            if person_is_seller(next_friend):
                yield path + [next_friend]
            else:
                queue.append((next_friend, path + [next_friend]))

我们使用列表模拟队列也是一样的, 你也可以使用列表来代替yield。这里其实还有一个问题,就是可能会重复计算。因为并没有像之前那样,把查找过的路径进行判断。由于我们的graph的数据只是单指向的,所以也并不会有什么问题,并且,这样更容易理解一些。下面解释了上面的代码做了什么:

  • 在queue里面存储每一个人(在图论中称为顶点,vertex),以及它的路径,用一个元组包裹。
  • 循环这个queue,每次只弹出最先进去的元素,得到一个元组(顶点,路径)
  • 根据元组的顶点得到graph的数据(朋友的朋友),遍历这些朋友
  • 如果不是要找的芒果商,那么把该朋友也添加到队列里面去,同时把路径也保存。

现在,在if判断加===,在else加打印queue,再给jonny添加一位朋友

graph['jonny'] = ['abcem']
[('alice', ['your', 'alice'])]
[('alice', ['your', 'alice']), ('boob', ['your', 'boob'])]
[('alice', ['your', 'alice']), ('boob', ['your', 'boob']), ('claire', ['your', 'claire'])]
[('boob', ['your', 'boob']), ('claire', ['your', 'claire']), ('peggy', ['your', 'alice', 'peggy'])]
[('claire', ['your', 'claire']), ('peggy', ['your', 'alice', 'peggy']), ('anuj', ['your', 'boob', 'anuj'])]
[('claire', ['your', 'claire']), ('peggy', ['your', 'alice', 'peggy']), ('anuj', ['your', 'boob', 'anuj']), ('peggy', ['your', 'boob', 'peggy'])]
===
[('peggy', ['your', 'alice', 'peggy']), ('anuj', ['your', 'boob', 'anuj']), ('peggy', ['your', 'boob', 'peggy']), ('jonny', ['your', 'claire', 'jonny'])]
===
[['your', 'claire', 'thom'], ['your', 'claire', 'jonny', 'abcem']]

可以看到,最近一度的朋友总是会被先探测到。这就是广度优先查找。

到此,基本的广度优先查找算法已经实现。

深度优先查找

深度优先查找,只要把队列变成栈即可。

def dfs(graph, start):
    queue = [(start, [start])]  # 1: ['A', ['A']]
    paths = []
    while queue:
        (vertex, path) = queue.pop()
        for next_friend in graph[vertex]:
            if person_is_seller(next_friend):
                print('===')
                paths.append(path + [next_friend])
            else:
                queue.append((next_friend, path + [next_friend]))
                print(queue)
    return paths
  • 第一次, 会把your的三个朋友压入栈
  • 弹出后入栈的朋友,找到他的朋友,再添加入栈,以此类推,每次出栈都会是最新添加的

由于每次出栈的都是新的数据,所以会深入一条一条朋友网的线找下去。这就是深度优先。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章