最短路径 Floyd 算法

最短路径Floyd 算法

  • 思路
    • 操作演示
    • 输出效果
  • C++ 代码

Floyd 算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,适用于正负权重的边和多源点。俗话说“遇事不决就 DP”,Floyd 算法是我比较喜欢的最短算法。

思路

如果看过 Dijkstra 算法,就会发现思路是一样的。依次取图中每一个点,更新其与邻接点的距离,即以其为中介检查图中两点之间通过此点是否比原来更短。
这里用了 C++ 来写,用了 class 方便处理数据。数据的表示上以二维数组 dist 记录各点之间距离。为了看清从X到Y要经过路程,我额外加了一个二维数组 via。
输入边时要检查有效性,如果有效在 dist 数组中更新X到Y距离,并在 via 数组中记录X到Y直达。
核心用到三层循环:最外层循环 i 依次以每个点为中介;由于是多源点问题,内层两个循环表示 j0 到 j1 依次检查是否可绕路 i,如果有新新路或者更短就更新 dist 和 via。
方法相比 Dijkstra 算法更简单,不用贪心算法找得那么麻烦,但时间复杂度有 O(n^3)。

操作演示

最短路径 Floyd 算法_第1张图片
还是以这个图为例。

初始状态应该是这样,即输入的各边距离,自环标记为0,不可直达距离设为一个很大数字就行。via 数组记录从X到Y经过哪个点,如果是直达就填Y,初始肯定是只记直达。
最短路径 Floyd 算法_第2张图片
现在以0为中介,发现1-0-2、1-0-3,更新。最短路径 Floyd 算法_第3张图片

然后以1为中介,发现新路径2-1-4、2-1-0、2-1-0-3。为什么能发现2到3的2-1-0-3?因为1-0-3是前面已知的,从1到3有记录,可以看成整体1(-0-)-3。
最短路径 Floyd 算法_第4张图片
以2为中介,发现新路径0-2-1、0-2-1-4、0-2-5、1-0-2-5。
最短路径 Floyd 算法_第5张图片
3没有出度。以4为中介点,发现1-4-3比1-0-3短
最短路径 Floyd 算法_第6张图片
以5为中介点,发现0-2-5-3比0-3短、2-5-3比2-0-3短。最短路径 Floyd 算法_第7张图片
到此结束。

输出效果

就是把二维数组非不可达的输出了,每个点都可以是起点:
最短路径 Floyd 算法_第8张图片
X到Y的路径也可以输出:最短路径 Floyd 算法_第9张图片
这次没用递归,说一下为什么 path 是正确的。其实就是一个逐次缩短问题的过程,以0-2-1-4为例:0-4先经过2;就要找2-4经过什么,经过1;然后找1-4经过什么,直达。

C++ 代码

我用了 class,省得二维数组传递太麻烦。n 记录点的数量,构造时指定;dist 是距离数组,via 是路径数组;以99999作为“不可达”标记。构造函数只用于开空间和初始化;然后 input 函数输入每条边;run 是计算函数;viewDist 查看距离,viewPath 查看路径;viewArray 是输出二维数组值的,没有启用,仅在 debug 时候用了一下。

#include 
#include 
#include 

using namespace std;

class Floyd
{
public:
    Floyd();
    void run();
    void viewDist();
    void input();
    void viewPath();
    void viewArray();
private:
    int** dist;
    int** via;
    int n = 0;
};

// 初始化类,并为二维数组开辟空间
Floyd::Floyd()
{
    int n;
    cin >> n;
    cin.get();
    if (n <= 0)
        return;
    this->n = n;
    // 初始化二维数组
    dist = new int* [n];
    via = new int* [n];
    for (short i = 0; i < n; i++)
    {
        dist[i] = new int[n];
        via[i] = new int[n];
    }
    // 此处假设99999是不通路距离,对自身到自身设为0
    for (short i0 = 0; i0 < n; i0++)
        for (short i1 = 0; i1 < n; i1++)
        {
            if (i0 == i1)
            {
                dist[i0][i1] = 0;
                via[i0][i1] = i0;
            }
            else
            {
                dist[i0][i1] = 99999;
                via[i0][i1] = 99999;
            }
        }
}

// 运行
void Floyd::run()
{
    if (n == 0)
        return;
    // 最外层循环是指当前以哪个点为中介
    for (short i = 0; i < n; i++)
    {
        // 内层两个循环依次检查各两点之间的距离
        for (short j0 = 0; j0 < n; j0++)
            for (short j1 = 0; j1 < n; j1++)
                if (dist[j0][j1] > dist[j0][i] + dist[i][j1])
                {
                    dist[j0][j1] = dist[j0][i] + dist[i][j1];
                    via[j0][j1] = i;
                }
        // viewArray();
    }
}

// 输出距离
void Floyd::viewDist()
{
    if (n == 0)
        return;
    for (short j0 = 0; j0 < n; j0++)
        for (short j1 = 0; j1 < n; j1++)
        {
            if (dist[j0][j1] == 99999 or j0 == j1)
                continue;
            cout << j0 << " to " << j1 << " dist: " << dist[j0][j1] << endl;
        }
}

// 输入每条边,按任意字符后输入【起点,终点,权值】整数,退出按q,还要输入按任意
void Floyd::input()
{
    if (n == 0)
        return;
    int start, end, value;
    while (cin.get() != 'q')
    {
        cin.get();
        cin >> start >> end >> value;
        if (start < 0 or n <= start or end < 0 or n <= end)
            cout << "error!" << endl;
        else
        {
            dist[start][end] = value;
            via[start][end] = end;
        }
        cin.get();
    }
}

void Floyd::viewPath()
{
    int viaNode;
    for (short j0 = 0; j0 < n; j0++)
    {
        for (short j1 = 0; j1 < n; j1++)
        {
            if (dist[j0][j1] == 99999 or j0 == j1)
                continue;
            cout << j0 << " to " << j1 << " path: ";
            viaNode = j0;
            // 如果不是直达,需要找所有中介
            while (viaNode != j1)
            {
                cout << viaNode << "-";
                viaNode = via[viaNode][j1];
            }
            cout << j1 << endl;
        }
        cout << endl;
    }
}

void Floyd::viewArray()
{
    for (short j0 = 0; j0 < n; j0++)
    {
        for (short j1 = 0; j1 < n; j1++)
            cout << setw(7) << dist[j0][j1];
        cout << endl;
    }
    cout << endl;
    for (short j0 = 0; j0 < n; j0++)
    {
        for (short j1 = 0; j1 < n; j1++)
            cout << setw(7) << via[j0][j1];
        cout << endl;
    }
}

int main()
{
    Floyd floyd;
    floyd.input();
    floyd.run();
    // floyd.viewArray();
    floyd.viewDist();
    floyd.viewPath();
    return 0;
}

你可能感兴趣的:(算法:最短路径)