图-遍历, since 2020.03.07

(2020.03.27)
图的遍历分为深度优先搜索和宽度优先搜索。

Depth-Firth Search(DFS)深度优先搜索:

DFS的栈实现

记录每个vertex是否被访问过,其流程如下:

  1. 初始化,设置一个数据结构(比如字典),记录每个结点是否被访问过,visited[i] = 0;

  2. 选择DFS的初始结点,如k,visited[k] = 1;

  3. 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)实现

流程如下:

  1. 初始化,设置一个数据结构(比如字典),记录每个结点是否被访问过,visited[i] = 0;

  2. 选择BFS的初始结点,如k,visited[k] = 1;

  3. 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 level i的顶点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,则该图联通。

针对有向图,可测试是否强联通性,也就是对任意顶点对uvu可达vv可达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.

你可能感兴趣的:(图-遍历, since 2020.03.07)