图,特别是图的ADT(抽象数据类型)在计算机科学和数学领域是应用非常广泛的。
图基本认识
图模拟一组连接,假如你在A点,你到B点的路径,可以使用图来代表。
graph.png
图由图由顶点(vertex,node)和边(edge)组成。一个顶点(vertex)可以与多个顶点连接,通过一条线,这条线称为边(edge)。
图用于模拟一组数据是如何连接的。
在Python中使用字典来实现这种连接关系。在之前的文章,我已經介绍了Python字典的实现原理。
广度优先搜索(breadth-first search, BFS)
首先,BFS可以解决两种问题
- 从节点A出发,能不能到节点X?
- 从节点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三个朋友压入栈
- 弹出后入栈的朋友,找到他的朋友,再添加入栈,以此类推,每次出栈都会是最新添加的
由于每次出栈的都是新的数据,所以会深入一条一条朋友网的线找下去。这就是深度优先。
网友评论