随着算法教学进度的推进,虽然关于图论的专题只开了头,讲了 DFS 和 BFS…
可是,万恶的计算机网络作业居然都是这样的题目,此处省略脏话 1000+ 字,本着离散数学曾经学过的良心,默默温习一下~但是,与此同时也算是对算法的一点点预习吧
(你心态真好!!喂,刚刚搞完 JAVA 的大作业,然后就得知马上要交网络的作业啊喂!!!
Dijkstra Algorithm
中文维基百科译为”戴克斯特拉”。很遗憾,这种算法不能解决权值为负的情况,可是谁叫我们一般情况下权值都是正值呢?它的主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
下面又开始了无耻的转载了……
算法思想
设 $G=(V,E)$是一个带权有向图,把图中顶点集合 $V$ 分成两组,第一组为已求出最短路径的顶点集合(用 $S$ 表示,初始时 $S$中只有一个源点,以后每求得一条最短路径 , 就将加入到集合 $S$ 中,直到全部顶点都加入到 $S$ 中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用 $U$表示),按最短路径长度的递增次序依次把第二组的顶点加入 $S$ 中。在加入的过程中,总保持从源点 $v$ 到 $S$ 中各顶点的最短路径长度不大于从源点 $v$ 到 $U$ 中任何顶点的最短路径长度。此外,每个顶点对应一个距离,$S$ 中的顶点的距离就是从 $v$ 到此顶点的最短路径长度,$U$ 中的顶点的距离,是从 $v$ 到此顶点只包括 $S$ 中的顶点为中间顶点的当前最短路径长度。
算法步骤
初始时,$S$ 只包含源点,即 $S=\{v\}$,$v$ 的距离为 0。$U$ 包含除 $v$ 外的其他顶点,即 $U=\{ \text{其余顶点} \}$,若 $v$ 与 $U$ 中顶点 $u$ 有边,则 $\langle u,v\rangle$ 正常有权值,若 $u$ 不是 $v$ 的出边邻接点,则 $\langle u,v\rangle$ 权值为 $\infty$。
从 $U$ 中选取一个距离 $v$ 最小的顶点 $k$,把 $k$,加入 $S$ 中(该选定的距离就是 $v$ 到 $k$ 的最短路径长度)。
以 $k$ 为新考虑的中间点,修改 $U$ 中各顶点的距离;若从源点 $v$ 到顶点 $u$ 的距离(经过顶点 $k$ )比原来距离(不经过顶点 $k$ )短,则修改顶点 $u$ 的距离值,修改后的距离值的顶点k的距离加上边上的权。
重复步骤 2 和 3 直到所有顶点都包含在 $S$ 中。
动画示例
代码实现
const int MAXINT = 32767; |
算法实例
给出一个无向图 $G$ 如下所示:
用 Dijkstra 算法找出以A为起点的单源最短路径步骤如下
转载自 华山大师兄 博客。
Bellman-Ford 算法
贝尔曼-福特算法,它的原理是对图进行 $V-1$ 次松弛操作,得到所有可能的最短路径。其优于 Dijkstra 算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达 $O(VE)$。
算法流程
给定图 $G(V, E)$(其中 $V$、$E$ 分别为图 $G$ 的顶点集与边集),源点 $s$ ,数组 Distant[i]
记录从源点 $s$ 到顶点 $i$ 的路径长度,初始化数组 Distant[n]
为 $\infty$,而 Distant[s]
为 0;
以下操作循环执行至多 $n-1$ 次,$n$ 为顶点数:
对于每一条边 $e(u, v)$ ,如果 $Distant[u] + w(u, v) < Distant[v]$
则令 $Distant[v] = Distant[u]+w(u, v)$。$w(u, v)$ 为边 $e(u,v)$ 的权值;
若上述操作没有对 Distant 进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;
为了检测图中是否存在负环路,即权值之和小于 0 的环路,对于每一条边 $e(u, v)$ ,如果存在 $Distant[u] + w(u, v) < Distant[v]$ 的边,则图中存在负环路,即是说该图无法求出单源最短路径。否则数组 Distant[n]
中记录的就是源点 $s$ 到各顶点的最短路径长度。
我个人倒是觉得有点像 DFS 啊,对刚刚更新的结点继续下一层的搜索、计算权值,取更小的那个作为新的权值。当每个结点在这一轮都不再更新的时候,算法结束。
算法的三部分
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为 0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从 $1$ 到 $n-1$($n$ 等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边 $edge(u,v)$,判断是否存在这样情况:
$d(v)>d(u) + w(u,v)$
则返回 false
,表示途中存在从源点可达的权为负的回路。
之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则应为无法收敛而导致不能求出最短路径。
可知,Bellman-Ford 算法寻找单源最短路径的时间复杂度为 $O(VE)$.
算法示例
代码实现
|
Bellman-Ford 算法转载自 WuTianQi 博客。
Floyd-Warshall 算法
算法简介
Floyd-Warshall 算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被用于计算有向图的传递闭包。Floyd-Warshall 算法的时间复杂度为 $O(N^3)$,空间复杂度为 $O(N^2)$。
算法描述
思想原理
Floyd 算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)
从任意节点 $i$ 到任意节点 $j$ 的最短路径不外乎两种可能,一是直接从 $i$ 到 $j$,二是从$i$经过若干个节点 $k$ 到 $j$。所以,我们假设 $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$ 的最短路径的距离。
算法过程
① 从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
② 对于每一对顶点 $u$ 和 $v$ ,看看是否存在一个顶点 $w$ 使得从 $u$ 到 $w$ 再到 $v$ 比己知的路径更短。如果是更新它。
Floyd 算法过程矩阵的计算——十字交叉法
方法:两条线,从左上角开始计算一直到右下角,如下所示
给出矩阵,其中矩阵A是邻接矩阵,而矩阵 $Path$ 记录 $u$, $v$两点之间最短路径所必须经过的点
$$A_{-1}=\left[\begin{array}{cccc}
0 & 5 & \infty & 7 \
\infty & 0 & 4 & 2 \
3 & 3 & 0 & 2 \
\infty & \infty &1 & 0\
\end{array}\right], Path_{-1}=\left[\begin{array}{cccc}
-1 & -1&-1 & -1 \
-1& -1 & -1 & -1 \
-1 & -1 & -1 & -1 \
-1 & -1 &-1 & -1 \
\end{array}\right]$$
相应计算方法如下:
最后 $A_3$ 即为所求结果。
算法代码
这种算法的代码写起来超级简单啊~
typedef struct |
算法时间复杂度: $O(n^3)$
Floyd-Warshall 算法转载自 华山大师兄 博客。