图的基础知识及深度优先、广度优先算法

图是一种可以表达复杂结构化信息的基本结构,关于图的例子可以自行百度。首先统一一下图中组成部分的叫法,这里把图中包含的元素叫做顶点,两两元素之间的关系叫做边。有时图中的边会带有更多的信息,比如边的方向,边的权值,方向好理解,权值指的可以是距离或者收益之类的附加信息。此外,为了减少不必要的考虑,假设图中的顶点不存在指向自己的边,两个顶点之间不存在多条边。下图中两种情况是不存在的。
不存在
做这些约束的目的也是明显的,通过上面的不存在情况的约束,我们可以很容易的用代码实现图这种结构。

s, a, b, c, d, e = range(6)
graph1 = [
    {a:6, b:8, c:18}, #s
    {d:11},#a
    {c:9},#b
    None,#c
    {e:3},#d
    {c:4, b:7},#e
]
graph2 = [
    [0, 6, 8, 18, 0, 0],#s
    [0, 0, 0, 0, 11, 0],#a
    [0, 0, 0, 9, 0, 0],#b
    [0, 0, 0, 0, 0, 0],#c
    [0, 0, 0, 0, 0, 3],#d
    [0, 0, 7, 4, 0, 0],#e
]

def dictionary(A):
    temp = []
    for l in A:
        tempRow = {}
        j = 0
        for r in l:
            if r != 0:
                tempRow[j] = r
            j = j + 1
        if len(tempRow) == 0:
            tempRow = None
        temp.append(tempRow)
    return temp

def matrix(A):
    temp = []
    for l in A:
        tempRow = [0] * len(A)
        if l is None:
            pass
        else:
            for k in l:
                tempRow[k] = l.get(k)
        temp.append(tempRow)
    return temp

上述代码中的graph1、graph2均是下图的一种表达形式。graph1中的每一行代表以行号为起点的边,用字典或None来表示每个起点与终点的关系,如果起点与终点存在边,那么字典就存储终点和权值作为字典的key和value;如果不存在任何终点就将这一行表示为None。graph2如果看成矩阵的话就是一个方阵An×n,行数和列数都是顶点数n。以行序号i为起点,列序号j为终点构成的边,如果存在的话就用Aij来存放权值,如果不存在就保存为0。如果图是无权图,这里不妨就把所有边的权值全部设为同一个值。如果图是无向图,这里也不妨将互为起点终点的一对边的权值设成一样。下图就是上述代码的图用图形表示的样子。
上述代码中还附加了两个函数dictionarymatrix,分别用来将graph2转化为graph1和将graph1转化为graph2。理解这两个函数的原理也有助于理解两种表达图的形式。
在这里插入图片描述

深度优先算法

想象一下走迷宫的过程,在没用办法知道迷宫的构造时,我们只能通过一条路走到黑再回头的笨办法。深度优先算法就是这样一种“笨办法”。

def depthFirstSearch(G, s):
    color = [0] * len(G)
    pred = [-1] * len(G)
    dfsVisit(G, s, pred, color)
    return pred

def dfsVisit(G, u, pred, color):
    color[u] = 1
    for v in G[u]:
        if color[v] == 0:
            pred[v] = u
            dfsVisit(G, v, pred, color)
    color[u] = -1

def routePrint(G, s, e):
    pred = depthFirstSearch(G, s)
    if pred[e] == -1:
        return None
    i = len(G)
    result = [e]
    temp = pred[e]
    if temp == s:
        result.append(s)
        return result
    while i > 0:
        result.append(temp)
        temp = pred[temp]
        i = i - 1
        if temp == s:
            result.append(s)
            return result

为了实现深度优先算法,depthFirstSearch创建了两个list:color、pred 。color是由于保存顶点颜色的,这里为了方便定义黑色为-1,白色为0,灰色为1;pred为前驱顶点,pred[i]为顶点i的前一个顶点的顶点编号。分别将color初始化为0,pred初始化为-1。这里解释一下颜色分为白、灰、黑的意义,白色表示顶点未被访问,灰色表示顶点被访问过但是存在未变访问的邻接顶点,黑色表示顶点自身及其邻接顶点均被访问过的顶点。dfsVisit输入图G、起点u、颜色表color、前驱顶点表pred,对u的邻接顶点进行遍历,u就是白色邻接顶点的前驱顶点,再对白色邻接顶点递归调用dfsVisitroutePrint是对深度优先算法的简单应用。输入图G、起点s、终点e,先对G使用深度优先算法获得前驱顶点表pred,pred[e]就是e的前驱顶点,不断迭代输出前驱顶点。如果起点生成的pred[e]为-1说明e、s之间不连通,返回None。

广度优先算法

广度优先算法顾名思义就是侧重在广度上对图进行分析,思路就是设法访问每个顶点,访问的原则是在访问距离原点S为k+1条边之前,所有距离为k条边的顶点都被访问过了。这样做的目的就是为了得到图中每个点距离原点S的最小边数。

import collections
def breadthFirstSearch(G, s):
    color = [0] * len(G)
    pred = [-1] * len(G)
    dist = [-1] * len(G)
    dist[s] = 0
    color[s] = 1
    queue = collections.deque()
    queue.append(s)
    
    while len(queue) > 0:
        u = queue[0]
        for v in G[u]:
            if color[v] == 0:
                dist[v] = dist[u] + 1
                pred[v] = u
                color[v] = 1
                queue.append(v)
        queue.popleft()
        color[u] = -1

breadthFirstSearch是广度优先算法的具体代码,比深度优先算法多了一个距离dist和队列queue,dist是用来记录与原点之间的边数,queue利用python自带的队列实现访问原则的约束。具体而言就是queue每次添加的元素都在队尾,取出的元素都在头部,保证距离为k的顶点先离队后才到距离为k+1的顶点。

总结

这里将用一个无权无向图来说明深度优先和广度优先的区别。
在这里插入图片描述
上图分别是深度优先和广度优先对图的探索结果,以0为起点得到的pred很明显的可以看出如果我们要探寻0到5经过的边,深度优先会给出012345,广度优先会给出045,广度优先算法能够给出较短的路径。那么深度优先有什么用呢?深度优先算法在一般情况下会更快地给出一条可行的路径,因为广度优先算法总会不厌其烦地记录每个分支。
下面是这次使用的图的相关代码。关于图的算法还有很多,这里只是入门性的介绍了广度优先和深度优先算法。后续还会有更多关于图的算法将会介绍。

G = [
    {1:1, 4:1}, #s
    {0:1, 2:1},#a
    {1:1, 3:1, 6:1},#b
    {2:1, 4:1},
    {0:1, 3:1, 5:1},#d
    {4:1},#e
    {2:1},
    {8:1},
    {7:1},
]
print(depthFirstSearch(G, 0))
print(breadthFirstSearch(G, 0))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值