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


    图论——最短路径问题

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

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

  • 相关阅读:
    《淘宝技术这十年》读书总结
    广告:互联网公司的纽带
    广告:互联网公司的纽带
    定期存款要及时
    定期存款要及时
    Java实现蓝桥杯VIP算法训练 自行车停放
    Java实现蓝桥杯VIP算法训练 自行车停放
    Java实现蓝桥杯VIP算法训练 自行车停放
    Java实现蓝桥杯VIP算法训练 数组逆序排列
    Java实现蓝桥杯VIP算法训练 数组逆序排列
  • 原文地址:https://www.cnblogs.com/ELAIRS/p/12822306.html
Copyright © 2020-2023  润新知