图论基础——单源最短路径问题

图论——最短路径问题

https://www.cnblogs.com/thousfeet/p/9229395.html

两点间最短路径问题:

例如求城市A到城市B之间最短距离

固定起始点,求最短路径

可以使用DFS,或者BFS


任意两点间的最短路径问题:

已知求解固定两点间最短路径的方法,如果要求任意两点间的最短路径,可以使用n^2次dfs或者bfs,但是时间复杂度较高

img

观察会发现,如果要让两点 i , j 间的路程变短,只能通过第三个点 k 的中转。比如上面第一张图,从 1->5 距离为10,但 1->2->5 距离变成9了。事实上,每个顶点都有可能使另外两个顶点间的路程变短。这种通过中转变短的操作叫做松弛。

当任意两点间不允许经过第三个点时,这些从城市之间的最短路程就是初始路程

例如有如下矩阵

img

假如现在允许经过1号顶点的中转,求任意两点间的最短路,这时候就可以遍历每一对顶点,试试看通过1号能不能缩短他们的距离。

for(int i = 1; i <= n; i++)
    for(int j = 1; j <= n; j++)
    {
        if(e[i][j] > e[i][1]+e[1][j]) e[i][j] = e[i][1]+e[1][j];
    }

更新后,3->2,4->2,4->3的路径都变短了

img

扩展一下,先允许1号顶点作为中转给所有两两松弛一波,再允许2号、3号...n号都做一遍,就能得到最终任意两点间的最短路了。

这就是Floyd算法,时间复杂度是O(n^3),但核心代码只有五行,实现起来非常容易

for(int k = 1; k <= n; k++)
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            if(e[i][j] > e[i][k]+e[k][j]) 
                e[i][j] = e[i][k]+e[k][j];

单源最短路径问题

即,指定源点,求它到其余各个结点的最短路径

比如给出这张图,假设把1号结点作为源点。

img

还是用数组dis来存1号到其余各点的初始路程:

img

既然是求最短路径,那先选一个离起点1号最近的结点,也就是2号结点。这时候,dis[2]=1 就固定了,它就是1到2的最短路径。因为目前离1号最近的是2号,且这个图的所有边都是正数,那就不可能能通过第三个结点中转使得距离进一步缩短了。因为从1号出发已经找不到哪条路比直接到达2号更短了。

选好了2号结点,现在看看2号的出边,有2->3和2->4。先讨论通过2->3这条边能否让1号到3号的路程变短,也即比较dis[3]和dis[2]+e[2][3]的大小。发现是可以的,于是dis[3]从12变为新的更短路10。同理,通过2->4也条边也更新下dis[4]。

松弛完毕后dis数组变为:

img

接下来,继续在剩下的 3 4 5 6 结点中选一个离1号最近的结点。发现当前是4号离1号最近,于是dis[4]确定了下来,然后继续对4的所有出边看看能不能做松弛。

这样一直做下去直到已经没有“剩下的”结点,算法结束。

这就是Dijkstra算法,整个算法的基本步骤是:

  1. 所有结点分为两部分:已确定最短路的结点集合P、未知最短路的结点集合Q。最开始,P中只有源点这一个结点。(可用一个book数组来维护是否在P中)
  2. 在Q中选取一个离源点最近的结点u(dis[u]最小)加入集合P。然后考察u的所有出边,做松弛操作。
  3. 重复第二步,直到集合Q为空。最终dis数组的值就是源点到所有顶点的最短路。
for(int i = 1; i <= n; i++) dis[i] = e[1][i]; //初始化dis为源点到各点的距离
for(int i = 1; i <= n; i++) book[i] = 0; 
book[1] = 1; //初始时P集合中只有源点

for(int i = 1; i <= n-1; i++) //做n-1遍就能把Q遍历空
{
    int min = INF;
    int u;
    for(int j = 1; j <= n; j++) //寻找Q中最近的结点
    {
        if(book[j] == 0 && dis[j] < min)
        {
            min = dis[j];
            u = j;
        }
    }
    book[u] = 1; //加入到P集合
    for(int v = 1; v <= n; v++) //对u的所有出边进行松弛
    {
        if(e[u][v] < INF) 
        {
            if(dis[v] > dis[u] + e[u][v]) 
                dis[v] = dis[u] + e[u][v];
        }
    }
}

Dijkstra是一种基于贪心策略的算法。每次新扩展一个路径最短的点,更新与它相邻的所有点。当所有边权为正时,由于不会存在一个路程更短的没扩展过的点,所以这个点的路程就确定下来了,这保证了算法的正确性。

但也正因为这样,这个算法不能处理负权边,因为扩展到负权边的时候会产生更短的路径,有可能破坏了已经更新的点路程不会改变的性质。

posted @ 2020-05-03 16:17  ELAIRS  阅读(788)  评论(0编辑  收藏  举报