当前文章来源于 掘金 觉得讲得相对不错。链接如下:数据结构与算法 - 图论
图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。
图论是一种表示 "多对多" 的关系
图是由顶点和边组成的:(可以无边,但至少包含一个顶点)
图可以分为有向图和无向图,在图中:
图可以分为有权图和无权图:
图又可以分为连通图和非连通图:
图中的顶点有度的概念:
图在程序中的表示一般有两种方式:
1. 邻接矩阵:
2. 邻接链表:
例如在无向无权图中:
在无向有权图中:
可以看出在无向图中,邻接矩阵关于对角线对称,而邻接链表总有两条对称的边
而在有向无权图中:
邻接矩阵和链表对比:
图的遍历就是要找出图中所有的点,一般有以下两种方法:
1. 深度优先遍历:(Depth First Search, DFS)
基本思路:深度优先遍历图的方法是,从图中某顶点 v 出发
伪码实现:
//伪码实现,类似于树的先序遍历
public void DFS(Vertex v){
visited[v] = true;
for(v 的每个邻接点 W){
if(!visited[W]){
DFS(W);
}
}
}
2. 广度优先搜索:(Breadth First Search, BFS)
广度优先搜索,可以被形象地描述为 "浅尝辄止",它也需要一个队列以保持遍历过的顶点顺序,以便按出队的顺序再去访问这些顶点的邻接顶点。
实现思路:
要理解深度优先和广度优先搜索,首先要理解搜索步,一个完整的搜索步包括两个处理
相当于在漆黑的夜里,你只能看清你站的位置和你前面的路,但你不知道每条路能够通向哪里。搜索的任务就是,给出初始位置和目标位置,要求找到一条到达目标的路径。
1. 无权图:
问题:在图中找到某一个顶点到其它所有点的距离
对于初始点 v 来说,某个点的 d 代表该点到初始点的距离。
基本步骤:
2. 有权图:
在有权图中,常见的最短路径算法有 Dijkstra 算法 Floyd 算法
迪杰斯特拉 Dijkstra 算法:Dijkstra 算法适用于权值为正的的图
Dijkstra 算法属于单源算法,即只能求出某点到其它点最短距离,并不能得出任意两点之间的最短距离。
算法步骤:
例如:
首先选取 v0 作为起始点,添加到优先队列中,将v0弹出,然后对 v0 邻接点进行判断,由于一开始所有边都为无穷大,那么 和 都更新,值为 2 和 1,按路径大小升序将v3、v1添加到优先队列。
之后将 v3 弹出,对所有 v3 邻接点进行值的更新,并将所有邻接点按路径大小升序添加到优先队列中,若遇到值相同,则无所谓其先后顺序
重复这样的过程,直到所有的点都被处理过,则算法终止,这样最后可以得出从 v0 到其它 v1~v6 节点的距离。
Dijkstra 算法适合于权值为正的情况下,若权值为负则不能使用,因为出现死循环。这时候我们需要计算每个顶点被处理的次数,当某个顶点已经处理过的话,就跳出该循环。
佛洛伊德 Floyd 算法:可以求出任意两点的最短距离
Floyd 算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点 i 到点 j 的最短路径。
从任意节点 i 到任意节点 j 的最短路径不外乎 2 种可能:
所以,我们假设 Dis(i,j) 为节点 u 到节点 v 的最短路径的距离,对于每一个节点 k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j) 是否成立,如果成立,证明从 i 到 k 再到 j 的路径比 i 直接到 j 的路径短,我们便设置 Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点 k,Dis(i,j) 中记录的便是 i 到 j 的最短路径的距离。
时间复杂度: O(n^3)
for(int k=0; k(A[i][k]+A[k][j])) {
A[i][j]=A[i][k]+A[k][j];
path[i][j]=k;
}
}
}
例如:要在 n 个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
特点:
存在个数:最小生成树在一些情况下可能会有多个
比如:
生成最小生成树的算法一般有两种,分别是 Prim 算法和 Kruskal 算法
1. 普里姆算法 (Prim 算法):
算法步骤:
时间复杂度:O(V^2)
2. Kruskal 算法:需要一个集合用来升序存储所有边
算法步骤:
时间复杂度:O( ElogV )
例如:
在对所有边进行排序之后,我们得到一个边集合,从边集合中取出最小权的边 AD
剩下的边中寻找。我们找到了 CE。这里边的权重也是 5,依次类推我们找到了 6,7,7
尽管现在长度为 8 的边是最小的未选择的边。但是他们已经连通了
最后就剩下 EG 和 FG 了。当然我们选择了 EG
当然,如果你看到这仍意犹未尽,想通过代码将上面的算法进行表示,我推荐下面的教程:
数据结构与算法系列 目录 - 如果天空不死 - 博客园
里面有对前面讲的算法进行详细实现。
之前有用 D3js 写过关于图论算法的动画,感兴趣地可以玩玩:Graph Animation
参考链接:
最小生成树-Prim算法和Kruskal算法 - as_ - 博客园