图是一种非常使用的数据结构,因为有非常多的场景都可以抽象成为结点和边的关系,而这些事物都可以用图来表示。弄清楚了图的一些基础算法,其实也可以在面对这些问题的时候有一个比较通用的解法。
花了一段时间,写了一些和图相关的一些基础算法。今天把这些文章整理一下,顺便也总结一下图相关的基础算法。
图的存储
图的存储,主要集中在要如何保存结点和边的关系上。不同的表示方法会有着不同的利弊,最好是根据具体的场景和使用的算法来决定要使用什么样的图存储方式。
主要的图存储数据结构有以下几种,我也在这两篇文章里面分别仔细的介绍了下原理和优劣势。(图的存储方式(一),图的存储方式(二))
邻接矩阵:使用矩阵的形式表示,不太适合存储边比较稀疏的图
邻接表/逆邻接表:适合表达稀疏矩阵,但如果即关心结点的入度,也关心结点的出度,使用起来就不太方便。
十字链表:综合了邻接表和逆邻接表,适合即关心入度也关心出度的算法,比如图的拓扑排序。
邻接多重表:比较适合需要频繁在图中增加结点的无向图,对图中的结点的增加和删除操作都比较方便。
边集数组:以边为主体的存储,适合对边操作的算法,比如:最小生成树的Kruskal算法,最短路径的Bellman-ford算法。
图的遍历
图的遍历主要有两种方式,一种是深度优先算法(图的遍历-深度优先),一种是广度优先算法(图的遍历-广度优先)。
这两种图的操作为后面很多算法奠定了基础,其中深度优先算法体现的是一种递归的思想,在每一个节点都会递归相同的下探操作。这种思想也体现在寻找图的拓扑排序时,深度优先的拓扑排序算法。
而广度优先算法使用的则是使用队列的思想,体现了一种处理事务的先后顺序。在拓扑排序的广度优先算法,Dijkstra算法里面都有广度优先的影子。只是在不同的算法中使用的队列会有一些不同。
图的最小生成树
图的最小生成树,就是一个图中,可以把所有节点连接起来,权重相加最短的边的集合。找到最小生成树主要有两种算法:
Prim算法(图的最小生成树-prim算法),他主要是从结点出发,每次都找到结点出去最小的那条边。
Kruskal算法(图的最小生成树-Kruskal算法),和Prim不同,他着眼的是边的视角。每次都找到最小的边,然后看看他会不会和现有的边形成一个环路。
图的最短路径
最短路径顾名思义,就是从一个节点到另一个结点权重之和最小的路径。面对不同的图和不同的任务会有不同的算法与之匹配。
Dijkstra算法(图的最短路径-Dijkstra算法):适用于有向图和无向图,但无法解决边有负权重的图。他是单源的最短路径算法,求出来的是某一个节点到其他各个节点的最短路径。
Floyd算法(图的最短路径-Floyd算法):他和Dijkstra算法差不多,但他是多源的最短路径算法,求出来的结果是所有结点到所有结点的最短路径矩阵。
Bellman-ford算法(负权图的最短路径-Bellman-Ford):他解决了Dijkstra无法计算负权图的最短路径问题,只要负权有向图不存在负权回路,Bellman-ford就可以计算出某个结点到各个结点的最短路径。但他的算法复杂度较高。
SPFA算法(负权图的最短路径-SPFA):Bellman-ford的优化算法,虽然最坏情况和Bellman-ford的时间复杂度一样,但平均的复杂度要低。
图的拓扑排序
拓扑排序,目的是找到有向图中是否存在环路。在某些复杂的图应用场景中,环路是不符合逻辑的,所以需要这样的算法系统性的验证图中是否有错误添加的环路。
图的拓扑排序有两种思路,一种是基于广度优先算法的思路(图的拓扑排序),算法实现中也使用了和广度优先算法相似的队列。一种是基于深度优先算法的思路(图的拓扑排序(二)),实现中也使用递归的方法。
图的关键路径算法
关键路径是制约一个流程时间的操作,在很多场景中,尤其是流水线业务中,关键路径意味着瓶颈点,找到关键路径是针对性优化的前提。
关键路径算法(图的关键路径算法),借助了拓扑排序算法和最短路径算法的一些技巧,使用了拓扑排序的结果和最短路径算法中的松弛数组。
总结
图和核心是结点和边,在各种各样的算法中,往往也都会有两种思路,一种是结点出发进行思考,比如Prim,Dijkstra;另一种是从边的角度进行思考,比如Kruskal,Bellman-ford。图的各种算法中,往往会借助两种工具,一种是队列,决定结点的操作顺序;一种是松弛列表,用来更新最短距离。
熟悉了图的处理思想和使用的工具,在遇到图的问题时,也可以借助这些套路尝试灵活的解决问题。希望这些总结可以有所帮助。
END
![b0e1288d18210a09111448ade06c675a.png](https://i-blog.csdnimg.cn/blog_migrate/b04b367118ca1d8c47583687ccea41f9.png)
作者:锅哥不姓郭
图片:网络(侵删)