读书笔记(python)--图及其算法

限于笔者技术水平,文章可能存在错漏,请各位不吝赐教,笔者会尽快改正


前言

持续更新中,笔者学艺不精,可能存在错漏,请各位不吝赐教,笔者会尽快改正。
本章节为《Python数据结构与算法分析(第2版)》第七章“图及其算法”的读书笔记,和原文的出入较大,需要学习图及其算法的小伙伴可以先去阅读原文。


一、图的术语与实现

顶点:顶点就是节点。
个人理解:这个节点按理来说,最好抽象为类,但也可以用数字、字符串、字典、列表等来表示,具体要看节点中包含什么东西。

:两个节点之间的连接关系,边可以是单向的,也可以是双向的。如果所有边都是单向的,则称为有向图。
个人理解:这个连接关系,并不一定要抽象为(起点, 终点, 权重)的形式,比如,如果没有权重,直接用 {起点1: [终点1, 终点2, 终点3, ...], ...} 的格式,也是足够用了。有权重时,是否可以使用{起点1: [(终点1, 权重1), (终点2, 权重2), (终点3, 权重3) ...]}的形式,有待商榷,更普遍的表达方式为:{起点1: {终点1: 权重1, 终点2: 权重2, 终点3: 权重3 ...}, ...}

权重:边可以带权重,用来表示从一个顶点到另一个顶点的成本。
个人理解:这个权重不一定是成本,也可以是收益,具体问题具体分析。

路径:路径是由边连接的顶点组成的序列。无权重路径的长度是路径上的边数,有权重路径的长度是路径上的边的权重之和。

:环是有向图中的一条起点和终点为同一个顶点的路径。没有环的图被称为无环图,没有环的有向图被称为有向无环图,简称为 DAG。
个人理解:有没有环,会对程序的流程产生影响,但简单的环,一般可以通过一些方式消除这种影响。

有两种非常著名的图实现,它们分别是邻接矩阵邻接表

邻接矩阵:要实现图,最简单的方式就是使用二维矩阵。在矩阵实现中,每一行和每一列都表示图中的一个顶点。第 v 行和第 w 列交叉的格子中的值表示从顶点 v 到顶点 w 的边的权重。如果两个顶点被一条边连接起来,就称它们是相邻的。
个人理解:比如4个顶点构成的有向环图 1->2->3->4->1,权重是(1,1,2,4),它的邻接矩阵可以是

[[None, 1, None, None],
[None, None, 1, None],
[None, None, None, 2],
[4, None, None, None]]

这种形式。

邻接表:为了实现稀疏连接的图,更高效的方式是使用邻接表。在邻接表实现中,我们为图对象的所有顶点保存一个主列表,同时为每一个顶点对象都维护一个列表,其中记录了与它相连的顶点。
个人理解:上述环图可以写成{1: {2: 1}, 2: {3: 1}, 3: {4: 2}, 4: {1: 4}}这种形式。


二、宽度优先(BFS)与深度优先(DFS)

1.一个简单的图

面试的时候,可能会遇到“写个BFS算法”或者“写个DFS”算法这样的问题。笔者第一次遇到这种问题时,完全是懵的,啥都没有,要我写啥?结果面试自然是一塌糊涂。

事实上,这种问题应该是很简单的,自己定义个简单的图,设定好起点和终点,然后用BFS算法解析下就可以了。

所以,这里笔者自己定义一个简单的无权重无向有环图。

graph这个图,由 0,1,2,3 这四个顶点组成了一个环,3-4-5形成一条链,画出来就是一个‘Q’的形状。

graph = {  # 这是一个简单的图(邻接表)
    0: [1, 2, 3],
    1: [0, 2, 3],
    2: [0, 1, 3],
    3: [0, 1, 2, 4],
    4: [5, 3],
    5: [4],
}

2.BFS代码

BFS即宽度优先(也称为广度优先)算法,它的宗旨是,一层层的遍历,把每个节点相邻的点先遍历一遍,再以相邻的点为基础,向外遍历,就像水波一样,一层一层的往外荡。

正常来说,graph需要作为参数传入,这里作为示例,就不再传入了,BFS代码及注释如下(如果你不想看注释,可以直接看后面的精简版代码):

def bfs(start_k, end_k):
    """
    :param start_k: 开始的位置 int型
    :param end_k: 结束的位置 int型
    :return: 列表,从start_k到end_k经过的节点
    """
    # 定义一个字典,key是graph的key,value为["颜色", "上一个节点(父节点)"]
    color_pred = {i: ["w", None] for i in graph}
    color_pred[start_k][0] = "g"  	# 准备遍历,将第一个顶点设为灰色
    queuing = [start_k]  			# 等待队列,这里面的节点都是灰色的
    
    while queuing:  				# 等待队列尚未清空,说明尚未构建完成
        grey_k = queuing.pop(0)  	# 依次提出灰色节点
        for t_k in graph[grey_k]:  	# 遍历当前灰色节点的相邻节点
            if color_pred[t_k][0] == "w":  			# 如果还没有被遍历到
                color_pred[t_k] = ["g", grey_k]  	# 置灰,设置父节点
                queuing.append(t_k)  				# 加入即将遍历的列表
            # todo 注意,以下 if 的这段只适用于当前这种图寻找路径
            if t_k == end_k:  		# 找到结束节点了,不用再遍历了
                queuing = []  		# 提前结束遍历
                break
        color_pred[grey_k][0] = "b"  # 遍历结束,当前灰色节点变黑色
    
    # 回溯:从最后一个节点开始,依次找到其父节点,组成一个列表
    pare_k = end_k
    ret_list = [pare_k]
    while color_pred[pare_k][1]:
        pare_k = color_pred[pare_k][1]
        ret_list.append(pare_k)
    if pare_k != start_k:
        raise ValueError(f"节点‘{start_k}’无法到达节点‘{end_k}’")
    return ret_list[::-1]

看起来有些复杂,去除注释和提前结束等组件后,基本程序是很简短的,而真正的核心,用于实现bfs过程的,其实只有6行代码:

def bfs(start_k, end_k):
	# 这部分是准备工作
    color_pred = {i: ["w", None] for i in graph}
    color_pred[start_k][0] = "g"
    queuing = [start_k]
    
   	# 这段是核心
    while queuing:
        grey_k = queuing.pop(0)
        for t_k in graph[grey_k]:
            if color_pred[t_k][0] == "w":
                color_pred[t_k] = ["g", grey_k]
                queuing.append(t_k)
        # color_pred[grey_k][0] = "b"  # 变成黑色这步,其实没必要
    
    # 这后面是回溯的过程
    ret_list = [end_k]
    while color_pred[end_k][1]:
        end_k = color_pred[end_k][1]
        ret_list.append(end_k)
    return ret_list[::-1]

3.DFS代码

BFS即深度优先算法,它的宗旨是,一条道走到黑,不撞南墙不回头,撞了南墙,先回个头,然后换个方向再撞。

笔者认为,能不用递归就不用递归,毕竟python递归的默认最大次数为1000(也有说是999的,但也差不了多少),虽然可以通过sys模块修改递归的最大次数,但并不是一个好的解决方案。最好还是能用循环的,就用循环解决,实在没法子,再用递归。
因此,笔者这里,只详细注释循环版的DFS代码。

注意,该代码只适用于上述类型的图,属于最简单的一种应用,其它类型的深度优先问题,需要进行一系列的修改,才能使用,比如这段代码是无法处理“骑士周游问题”的。

一次深度优先搜索甚至能够创建多棵深度优先搜索树,我们称之为深度优先森林。这段代码没有实现深度优先搜索树,所以它获得的路径,不一定是最优路径。注意,即便有深度优先搜索树的存在,也不一定是最优路径。(面试可能会问到深度优先搜索树,拓扑排序也会用到,所以笔者之后会加上带深度优先搜索树的DFS)

DFS代码,如果没有获得到达的路径,这种方法会报错:

def dfs(start_k, end_k):
    # 准备工作和bfs是一样的
    color_pred = {i: ["w", None] for i in graph}
    color_pred[start_k][0] = "g"
    queuing = [start_k]

    # 核心方法并不相同
    while queuing:
        grey_key = queuing[-1]          # 最后一个节点,即本次搜索的起点
        # 查询当前节点相邻的白色节点,如果周边没有白色节点,则当前节点置黑
        k_list = [k for k in graph[grey_key] if color_pred[k][0] == "w"]
        if k_list:                      # 如果还有白色节点,则继续深入
            new_k = k_list[0]           # new_k可以是k_list中任意元素
            queuing.append(new_k)		# 设置下一步的起点为new_k
            if new_k == end_k:          # 找到end_k了,提前结束搜索
                break
            color_pred[new_k][0] = "g"  # 将新的k置为灰色
        else:
            # color_pred[start_k][0] = "b"  # 置为黑色,这句没必要
            queuing.pop()               # 回头
    else:
        raise ValueError(f"节点‘{start_k}’无法到达节点‘{end_k}’")
    return queuing
    

精简版代码(循环版),这种方法如果没有获得结果,则返回空列表:

def dfs_1(start_k, end_k):
    # 准备工作
    node_color = {i: "w" for i in graph}
    node_color[start_k] = "g"
    queuing = [start_k]

    # 核心方法
    while queuing:
        k_list = [k for k in graph[queuing[-1]] if node_color[k] == "w"]
        if k_list:
            new_k = random.choice(k_list)  # 这样也是可以的
            queuing.append(new_k)
            if new_k == end_k:
                break
            node_color[new_k] = "g"
        else:
            queuing.pop()  # 回头

    return queuing

精简版代码(递归版),这种方法如果没有获得结果,则返回None:

node_color = {i: "w" for i in graph}
queuing = []

def dfs(start_k, end_k):
    queuing.append(start_k)
    if start_k == end_k:
        return queuing
    node_color[start_k] = "g"
    for k in graph[start_k]:
        if node_color[k] == "w" and dfs(k, end_k):
            return queuing
    else:  # 说明已经无路可走了
        queuing.pop()  # 只能回头

4.骑士周游

骑士周游问题是一个非常经典的DFS求解问题:取一块国际象棋棋盘和一颗骑士棋子(马),目标是找到一系列走法,使得骑士对棋盘上的每一格刚好都只访问一次。

骑士周游问题的简略版代码,这个代码还是比较复杂的,但它的核心功能 kt_core 并不长,与上面的DFS算法的主要区别是结束条件不同,以及 ‘当前节点恢复为白色’ 这样一个操作,上面的DFS是置为黑色,表示当前节点不可再次访问,但在骑士周游问题上,如果从当前节点回退,之后还是有可能再次访问到当前节点的。

简略版代码如下:

class KnightTour(object):
    def __init__(self, bd_size):
        """
        :param bd_size: 棋盘为 bd_size * bd_size 大小
        """
        self.bd_size = bd_size
        self.node_num = bd_size * bd_size  		# 总节点数
        # 每一个节点,最多有8个运动方向
        self.move_8 = [(1, 2), (2, 1), (1, -2), (2, -1), (-1, 2), (-2, 1), (-1, -2), (-2, -1)]
        self.kt_grape = self.get_kt_grape()  	# 获得图的邻接表

    def get_kt_grape(self):  					# 笔者习惯把行写作i,列写作j,应写为row和col
        return {(i, j): self.get_connections(i, j) for i in range(self.bd_size) for j in range(self.bd_size)}

    def get_connections(self, i, j):  			# 计算(i, j)的可用连接点
        ret_list = list()
        for i_p, j_p in self.move_8:
            new_i, new_j = i + i_p, j + j_p
            if 0 <= new_i < self.bd_size and 0 <= new_j < self.bd_size:  # 判断是否还在棋盘上
                ret_list.append((new_i, new_j))
        return ret_list

    def knight_tour(self, start_pos):
        """
        :param start_pos: (列号,行号)
        :return: list 格式:[(列号,行号), (列号,行号), ...]
        """
        pos_list = list()
        node_color = {pos: "w" for pos in self.kt_grape}
        return self.kt_core(start_pos, pos_list, node_color)

    def kt_core(self, start_pos, pos_list, node_color):  # 核心代码
        pos_list.append(start_pos)
        node_color[start_pos] = "g"
        if len(pos_list) < self.node_num:
            nbr_list = self.kt_grape[start_pos][:]
            # 引导代码,优先遍历那些有效连接点更少的节点,没有这句排序,可能会很慢
            nbr_list.sort(key=lambda x: len([1 for pos in self.kt_grape[x] if node_color[pos] == "w"]))
            for nbr in nbr_list:  			# 遍历白色的邻居节点
                if node_color[nbr] == "w" and self.kt_core(nbr, pos_list, node_color):
                    return pos_list
            # 遍历完成,没有获得满意的结果,只能回头
            node_color[start_pos] = "w"  	# 当前节点恢复为白色
            pos_list.pop()  				# 回头
            return False  					# 当前走法下,当前节点不满足要求
        else:
            return pos_list

    def kt_core_s(self, start_pos, pos_list, node_color):  # 核心代码,去掉引导和注释
        pos_list.append(start_pos)
        node_color[start_pos] = "g"
        if len(pos_list) < self.node_num:
            for nbr in self.kt_grape[start_pos]:
                if node_color[nbr] == "w" and self.kt_core_s(nbr, pos_list, node_color):
                    return pos_list
            node_color[start_pos] = "w"
            pos_list.pop()
            return False
        return pos_list


if __name__ == '__main__':
    board_size = 8
    kt_obj = KnightTour(board_size)
    print(kt_obj.knight_tour((2, 0)))

5.通用搜索树

一次深度优先搜索甚至能够创建多棵深度优先搜索树,我们称之为深度优先森林。和宽度优先搜索类似,深度优先搜索也利用前驱连接来构建树。
个人理解:这个说明看的笔者直挠头,没明白这个深度优先搜索树到底是什么。笔者猜测,应该和宽度优先搜索产生的树是一个类型的东西,知道起点,可以创建一个树,知道终点后,从终点回溯这棵树,就能找到父节点,最终回溯到起点。

深度优先搜索树可能不止一种构建方式,遍历的先后顺序,可能影响树的形状、父子节点关系等等。

依然是简易版代码,构建一个简单的深度优先搜索树,图还是最上面的graph,下面代码中的color_pred存储了节点的信息,可以把这个东西看成一棵树(虽然它确实抽象了一些)。

color_pred = {i: ["w", None, None, None] for i in graph}  # [颜色, 父节点, 开始时间, 结束时间]

def dfs():
    time = 0
    for k in color_pred:
        if color_pred[k][0] == 'w':
            time = dfs_visit(k, time)

def dfs_visit(start_k, time):
    color_pred[start_k][0] = "g"
    time += 1
    color_pred[start_k][2] = time
    for k in graph[start_k]:
        if color_pred[k][0] == "w":
            color_pred[k][1] = start_k
            time = dfs_2(k, time)
    time += 1
    color_pred[start_k][3] = time
    return time

同样的理念,可以整一个BFS搜索树出来(这个是无向图的代码,有向图需要改动)。

color_pred = {i: ["w", None] for i in graph}  # [颜色, 父节点]

def bfs(start_k):
    color_pred[start_k][0] = "g"
    queuing = [start_k]
    while queuing:
        grey_k = queuing.pop(0)
        for k in graph[grey_k]:
            if color_pred[k][0] != "w":
                continue
            color_pred[k][0] = "g"
            queuing.append(k)
            color_pred[k][1] = grey_k

个人理解:这两种搜索树,对无向环图也是适用的,但是,如果用在环图身上,这个搜索树不一定有现实意义,好像除了找两个顶点间的路径,并不能带来其它帮助。特别是无向环图的情况下,同一个起点构建出的不同树,父子节点在不同的树上,关系可能是相反的。


三、拓扑排序

1.DFS解法

拓扑排序根据有向无环图生成一个包含所有顶点的线性序列,使得如果图 G 中有一条边为(v, w),那么顶点 v 排在顶点 w 之前。
个人理解:这个东西,主要在一个有向无环图,其它的和dfs解析树的构建没啥不同,因为有向无环,所以应当添加一个判断,如果dfs从灰色节点沿着一条道路出发,又走到了灰色节点上,那么,它一定是有环的,此时,拓扑排序会失去意义。

这个图得换成有向无环图了,代码只需在dfs解析树上加上对灰色节点的判断:

graph = {0: [3], 1: [3], 2: [3], 3: [5, 6], 4: [5], 5: [7], 6: [8], 7: [8], 8: []}
color_pred = {i: ["w", None, None, None] for i in graph}  # [颜色, 父节点, 开始时间, 结束时间]

REVERSE = True  # 这个为True可以尽量保证小序号在前,False则是大序号尽量在前


def dfs():
    time = 0
    for k in sorted(color_pred, reverse=REVERSE):
        if color_pred[k][0] == 'w':
            time = dfs_visit(k, time)
    return [i[0] for i in sorted(list(color_pred.items()), key=lambda x: x[1][-1], reverse=True)]


def dfs_visit(start_k, time):
    color_pred[start_k][0] = "g"
    time += 1
    color_pred[start_k][2] = time
    for k in sorted(graph[start_k], reverse=REVERSE):
        if color_pred[k][0] == "w":
            color_pred[k][1] = start_k
            time = dfs_visit(k, time)
        elif color_pred[k][0] == "g":
            raise ValueError("有环!")
    color_pred[start_k][0] = "b"  # 这句就不能省了
    time += 1
    color_pred[start_k][3] = time
    return time


if __name__ == '__main__':
    print(dfs())

按结束时间,从大到小排序,就可以构建出一个有序列表,这就是拓扑排序的结果。

显然,拓扑排序可能不止一种结果,可能不止一个有序列表是正确答案,有些问题会在拓扑排序基础上,要求序列号尽量从小到大等,可以通过开启REVERSE来实现(注意:这个排序的方法是笔者自己想出来的,是否正确需要验证),这里暂时不做讨论。

2.BFS解法

笔者目测,这位大佬的文章,貌似就是典型的BFS解法(笔者不确定,有懂行的兄弟姐妹评论区说一说,非常感谢):
拓扑排序入门(真的很简单)
个人理解: BFS需要入度为0的点作为起始点,一步步剥离出所有的点,所以它也不需要啥开始结束时间,但需要统计入度。另外,DFS貌似没有起始点的限制。

代码,注意,这里的REVERSE和上面的DFS解法(这个排序的方法是笔者自己想出来的,是否正确需要验证),作用恰好相反:

graph = {0: [3], 1: [3], 2: [3], 3: [5, 6], 4: [5], 5: [7], 6: [8], 7: [8], 8: []}
color_pred = {i: ["w", 0] for i in graph}  # [颜色, 入度]

REVERSE = False  # 这个为True可以尽量保证大序号在前,False则是小序号尽量在前


def bfs():
    for k, v in graph.items():  # 先计算入度
        for i in v:
            color_pred[i][-1] += 1
    queuing = [i for i, v in color_pred.items() if v[1] == 0]  # 取出所有入度为0的
    for k in queuing:
        color_pred[k] = "gray"
    ret_list = list()  # 拓扑排序结果
    while queuing:
        queuing.sort(reverse=REVERSE)
        start_k = queuing.pop(0)
        ret_list.append(start_k)
        for nbr in graph[start_k]:
            color_pred[nbr][1] -= 1  # 入度-1
            if color_pred[nbr] == ["w", 0]:
                queuing.append(nbr)
                color_pred[nbr][0] = "g"
    if len(ret_list) != len(graph):
        raise ValueError("有环!")
    return ret_list

四、强连通单元

通过一种叫作强连通单元的图算法,可以找出图中高度连通的顶点簇。
个人理解,这玩意儿就是用来算环的,具体有啥用,不知道。

把上面的dfs报错改成break,就可以获得带“环”的“拓扑排序”(不是真的拓扑排序),按这个顺序,遍历转置后的图,就可以获得一个深度优先森林,可能具备多棵树,每个树上的顶点,可以分为一组。

def scc(graph):
    graph_reverse = {k: [] for k in graph}
    for k, v_list in graph.items():
        for v in v_list:
            graph_reverse[v].append(k)
    color_pred = {i: ["w", None, None, None] for i in graph}  # [颜色, 父节点, 开始时间, 结束时间]
    sort_list = sorted(color_pred)
    tp_sort = dfs(color_pred, sort_list, graph)
    color_reverse = {i: ["w", None, None, None] for i in graph}
    group_sort = dfs(color_reverse, tp_sort, graph_reverse)
    return grouping(color_reverse, group_sort)


def grouping(color_pred, group_sort):
    group_list = []
    group_x = [group_sort[0]]
    end_p = group_sort[0]
    for k in group_sort[1:]:
        if color_pred[k][2] > color_pred[end_p][2] and color_pred[k][3] < color_pred[end_p][3]:
            group_x.append(k)
        else:
            group_list.append(group_x)
            group_x = [k]
            end_p = k
    else:
        group_list.append(group_x)
    return group_list


def dfs(color_pred, sort_list, graph):
    time = 0
    for k in sort_list:
        if color_pred[k][0] == 'w':
            time = dfs_visit(color_pred, k, time, sort_list, graph)
    return [i[0] for i in sorted(list(color_pred.items()), key=lambda x: x[1][-1], reverse=True)]


def dfs_visit(color_pred, start_k, time, sort_list, graph):
    color_pred[start_k][0] = "g"
    time += 1
    color_pred[start_k][2] = time
    for k in sort_list:
        if k not in graph[start_k]:
            continue
        if color_pred[k][0] == "w":
            color_pred[k][1] = start_k
            time = dfs_visit(color_pred, k, time, sort_list, graph)
        elif color_pred[k][0] == "g":
            # raise ValueError("有环!")
            break
    color_pred[start_k][0] = "b"  # 这句就不能省了
    time += 1
    color_pred[start_k][3] = time
    return time


if __name__ == '__main__':
    graph_1 = {0: [3], 1: [3], 2: [3], 3: [5, 6], 4: [5], 5: [7], 6: [8], 7: [8], 8: [9], 9: [7]}
    print(scc(graph_1))

五、最短路径问题

我们要解决的问题是为给定信息找到权重最小的路径。这个问题并不陌生,因为它和我们之前用宽度优先搜索解决过的问题十分相似,最短路径问题,只不过现在考虑的是路径的总权重,而不是路径的长度。需要注意的是,如果所有的权重都相等,那么两个问题就没有区别。
个人理解:最开始的理解是两节点之间的最短路径,但后来认为,不仅是单纯的两个节点之间,有可能要看整体权重,但总的来说,就是令某种条件下的权重最小。这个东西如果没有权重,或者权重都一样,那么算法的意义会有一定的折扣。

1.Dijkstra算法

个人理解:这个算法的目标,是创建一个树,使得每个节点都和start节点之间的路径权重最小,这个算法和广度优先算法非常相似。

实现代码如下(简易版代码):

def pop_min(queuing):
    queuing_reverse = {v: k for k, v in queuing.items()}
    min_weight = min(list(queuing_reverse))
    ret_k = queuing_reverse.pop(min_weight)
    queuing.pop(ret_k)
    return ret_k


def dijkstra(graph, start):
	# 为了更高的效率,可以考虑把queuing换成最小堆,这里为了简化代码就不实现了
    color_pred = {i: [None, 100000] for i in graph}  # [父节点,距离初始节点的总权重]
    color_pred[start] = [None, 0]
    queuing = {start: 0}  # 这里为了便于取出最小权重,也备份了一个权重
    while queuing:
        start_k = pop_min(queuing)  # 每次取出与start节点最近的节点
        for nbr, weight in graph[start_k].items():
            new_dis = color_pred[start_k][1] + weight
            if new_dis < color_pred[nbr][1]:  # 通过新节点可以更快的到达start
                color_pred[nbr] = [start_k, new_dis]  # 刷新父节点与总权重
                queuing[nbr] = new_dis
    return color_pred


if __name__ == '__main__':
    my_graph = {  # 一个有权重的无向图
        "u": {"v": 2, "w": 5, "x": 1},
        "v": {"u": 2, "w": 3, "x": 2},
        "w": {"u": 5, "v": 3, "x": 3, "y": 1, "z": 5},
        "x": {"u": 1, "v": 2, "w": 3, "y": 1},
        "y": {"w": 1, "x": 1, "z": 1},
        "z": {"w": 5, "y": 1}
    }
    print(dijkstra(my_graph, "u"))  # 开始节点设置为u

2.Prim 算法

由于每一步都选择代价最小的下一步,因此 Prim 算法属于一种“贪婪算法”。在这个问题中,代价最小的下一步是选择权重最小的边。
个人理解:这个算法的目标,是创建一个权重总值最小的树,而不考虑和start之间的距离,但是,原文中的代码却和Dijkstra算法相同,这令笔者十分费解。
于是笔者翻阅了其他作者的文章,比如这位大佬的文章:
最小生成树——Prim算法(详细图解)
笔者认为,应当是《Python数据结构与算法分析(第2版)》第七章“图及其算法” 代码清单 7-12 Prim 算法的 Python 实现 出现了错误,所以,笔者将按自己的理解,写一个简易版的代码。

这段代码和Dijkstra算法使用同一套输入:

def prim(graph, start):
    pred = {i: None for i in graph}  # 父节点
    queuing = {i: 100000 for i in graph}  # 权重
    queuing[start] = 0
    while queuing:
        start_k = pop_min(queuing)
        for nbr, weight in graph[start_k].items():
            if nbr in queuing and weight < queuing[nbr]:
                pred[nbr] = start_k
                queuing[nbr] = weight
    return pred

总结

原文的小结:对于解决下列问题,图非常有用。
 利用宽度优先搜索找到无权重的最短路径。
 利用 Dijkstra 算法求解带权重的最短路径。
 利用深度优先搜索来探索图。
 利用强连通单元来简化图。
 利用拓扑排序为任务排序。
 利用最小生成树广播消息。

个人理解:
笔者在实际工作中,几乎没遇到过图相关的问题,但是,它面试总是被问到,从简单的如何设计和存储一个图,到非常坑爹的应用题,那真是,包罗万象。
不论是否会在实际工程中使用,图都是必须面对的问题,还是好好学一下吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值