(2020.03.27)
图的遍历分为深度优先搜索和宽度优先搜索。
Depth-Firth Search(DFS)深度优先搜索:
DFS的栈实现
记录每个vertex是否被访问过,其流程如下:
初始化,设置一个数据结构(比如字典),记录每个结点是否被访问过,visited[i] = 0;
选择DFS的初始结点,如k,visited[k] = 1;
k塞入一个栈结构,S;
# 2020.12.24 Thur
if __name__ == '__main__':
g = {} #a dict storing the vertex info
g.update({'a': {'b': 1, 'c': 3, 'd': 5}, 'b': {'a': 1, 'e': 2, 'k': 10}}) #顶点a的邻居有b,c,d,权重分别为1,3,5。以此类推。
visited = {'a':0,'b':0,'c':0,'d':0,'e':0,'k':0}
s = stack()
s.push(a) # a as starting point
result = []
while not s.is_empty():
x = s.top()
visited[x] = 1
# result.append operation here?
for uv in g[x].keys():
if visited[uv] == 0:
s.push(uv)
visited[uv] = 1
result.append(uv)
s.pop() # pop out x
(2022.07.04 Mon)
DFS的递归实现
给定初始顶点u
,和discovered
字典,该字典保存访问过的节点,以访问过的节点为key,以途径的edge为value。从初始点开始,通过它附属的边,找到边的另一个顶点,并标记为discovered。重复该步骤,遍历联通图中的所有顶点。
def DFS(g, u, discovered):
"""
Arguments:
g: the graph
u: starting point
discovered: the map to store the edge (value) from which the vertex (key) is discovered
"""
for e in g.incident_edges(u):
v = e.opposite(e)
if v not in discovered:
discovered[v] = e
DFS(g, v, discovered)
以下面方式开始遍历
>> result = {u: None}
>> DFS(g, u, result)
上面的遍历仅限于一个图是联通的情况,对于无法联通的,可通过遍历该图所有顶点+调用DFS
函数的方式完成DFS。
def DFS_complete(g):
"""
DFS in a recursive way, no stack used
Arguments:
g: the graph in adjacent map format
"""
forest = {}
for v in g.vertices():
if v not in forest:
forest[v] = None
DFS(g, v, forest)
return forest
Breadth-First Search(BFS)广度优先搜索:
(2020.03.08 Sun)
BFS的队列(queue)实现
流程如下:
初始化,设置一个数据结构(比如字典),记录每个结点是否被访问过,visited[i] = 0;
选择BFS的初始结点,如k,visited[k] = 1;
k塞入一个队列queue, S;
4
while S非空:
x=S首元素出队列;
W = x的邻域点集合;
for w in W:
if visited[w] == 1: continue
else: visited[w] = 1 并且w进S队列 (为标记任一点距离首发点的距离可将w与距离组成元组加入S)
复杂度: n个顶点和m条边的BFS复杂度O(n+m).
# 2020.12.24 Thur
if __name__ == '__main__':
g = {} #a dict storing the vertex info
g.update({'a': {'b': 1, 'c': 3, 'd': 5}, 'b': {'a': 1, 'e': 2, 'k': 10}}) #顶点a的邻居有b,c,d,权重分别为1,3,5。以此类推。
visited = {'a':0,'b':0,'c':0,'d':0,'e':0,'k':0}
q = queue()
q.enqueue(a)
visited[a] = 1
result = []
while not q.is_empty():
x = q.dequeue()
result.append(x)
neighbours = g[x].keys()
for w in neighbours:
if visited[w] == 1:
continue
else:
visited[w] = 1
q.enqueue(w)
(2022.07.04 Mon)
BFS的递归实现
递归实现时,一个顶点的所有临近节点按层(level)保存,这个操作也符合按宽度检索。对每个level做遍历,可以实现BFS。
def BFS(g, s, discovered):
"""
conduct BFS in a recursive manner, use next_level to store adjacent vertices
Arguments:
g: graph
s: starting point
discovered: the map to store the edge (value) from which the vertex (key) is discovered
"""
level = [s]
while len(level) > 0:
next_level = []
for u in level:
for e in g.incident_edges(u):
v = e.opposite(u)
if v not in discovered:
discovered[v] = e
next_level.append(v)
level = next_level
定理
图G有n个顶点和m个边,以adjacency list的结构保存,则BFS遍历G的时间复杂度为
图G采用BFS遍历,不管是指向图还是非指向图,始于顶点s,则
1 遍历将访问顶点s可达的所有顶点
2 leveli
的顶点v,从起点s到顶点v的BFS树有i
个边,而从s到v的其他路径至少有i
个边
3 如果边(u, v)
不在BFS树中,则v的level number比u的level number至少大1
(2022.07.04 Mon)
应用
图的遍历,特别是DFS,有多种应用。
从顶点u到v的路径 reconstructing a path from u to v
该方法应用DFS的检索结果,特别是discovered的结果。做反向遍历,从v到u,并在遍历过程中记录路径path,得到预期结果。
def constructed_path(u, v, discovered):
path = []
if v in discovered:
path.append(v)
walk = v
while walk is not u:
e = discovered[walk]
parent = e.opposite(walk)
path.append(parent)
walk = parent
path.reverse()
return path
检测图的联通性
根据前面提到的定理,即遍历将抵达所有可达的顶点,可以用遍历方法检测图的联通性。执行遍历操作之后,检测discovered
这个数据机构的长度,如果该长度等于图的顶点数n
,则该图联通。
针对有向图,可测试是否强联通性,也就是对任意顶点对u
和v
,u
可达v
且v
可达u
。完整检测每个点的强连通性,即对n个点的每个点都执行DFS操作,时间复杂度为。
下面的方法可以保证仅需要两次DFS就可确定有向图的强联通性:第一次,从图G中任选一点s
开始DFS,如果G中有任何一点没有在遍历结果中,则不能从s
抵达,则图不是强联通。如果第一次遍历的结果包含了所有顶点,则我们需要判断点s
是否从其他顶点可达。概念上,可以建立图G的拷贝,并且将所有边的方向调转。在图的拷贝中,从点s
开始做DFS,如果能抵达所有点,则该图强联通。实际操作中,避免建立图的拷贝,而使用当前节点的输入边(incoming edges)而输出边(outgoing edges)做循环。
这种方法仅需两次遍历,因此时间复杂度为。
对于非联通图,可使用DFS中的DFS_complete
方法实现对所有顶点的遍历。
Reference:
1 M.T. Goodrich and etc., Data Structures and Algorithms in Python.