最短路径、Dijkstra算法、Floyd算法

最短路径

非网图中,最短路径是指两个顶点之间经历的边数最少的路径,路径上的第一个顶点称为源点,最后一个顶点称为终点

网图中,最短路径是指两点之间经历的边上的权值之和最少的路径。

Dijkstra算法

Dijkstra算法用于求单源点路径问题,问题描述如下:给定带权有向图 G = ( V , E ) G=(V,E) G=(V,E) 和源点 v ∈ V v \in V vV,求从v到G中其余各顶点的最短路径。

迪杰斯特拉提出了一个按路径长度递增的次序产生最短路径的算法,其基本思想是:设置一个集合 S 存放已经找到最短路径的顶点,S的初始状态只包含源点 v v v,对 v i ∈ V − S v_i \in V-S viVS ,假设从源点 v , . . . , v k v,...,v_k v,...,vk ,就将 v k v_k vk 加入集合 S 中,并将路径 v , . . . , v k v,...,v_k v,...,vk v i v_i vi 与原来的假设相比较,取路径长度较小者为当前最短路径。

最短路径:

// Dijkstra最短路径
/*
g:邻接矩阵
n:顶点个数
v_start:源点

return:源点到其他点的最短路径经过的结点
*/
int** graph_shortestPath_Dijkstra(int** g, int n, int v_start)
{
    // 临时最短路进长度的数组
    int* arr_sp = utils_createArr(n, -1);
    // 存储路径
    int** arr_sp_path = utils_create2DArr(n,n,-1);

    // 初始化数组和路径
    for(int i = 0; i < n ; i ++)
    {
        // 如果可达,初始化路径
        if(i != v_start && g[v_start][i] != -1)
        {
            arr_sp_path[i][1] = i;
            arr_sp[i] = g[v_start][i];
            arr_sp_path[i][0] = v_start;
        }
    }

    // 标记源点为已经找到最短路径
    arr_sp[v_start] = 0;

    int counter = 0, k;
    while(++counter < n)
    {
        k = -1;
        // 找 未找到最短路径的最小值
        /*
        -1:表示无法抵达
        0:表示已经找到最短路径
        */
        for(int i = 0; i < n; i ++)
        {
            // 跳过不可抵达的和已经找到最短路径的点
            if(arr_sp[i] == -1 || arr_sp[i] == 0) continue;

            if(k == -1 || (k != -1 && arr_sp[i] < arr_sp[k])) k = i;
        }

        // 若不可达,跳过
        if(k == -1) continue;

        // 将 k 顶点加入集合
        for(int i = 0; i < n; i ++)
        {
            // 跳过不存在的边
            if(g[k][i] == -1) continue;
            // 跳过已经找到最短路径的边
            if(arr_sp[i] == 0) continue;

            // 若可得到更小的路径,替代之
            if(arr_sp[i] == -1 || arr_sp[k] + g[k][i] < arr_sp[i])
            {
                arr_sp[i] = arr_sp[k] + g[k][i];

                // 用新路径顶点覆盖老路径顶点
                int j = 0;
                for(; j < n; j ++)
                {
                    if(arr_sp_path[k][j] == -1) break;

                    arr_sp_path[i][j] = arr_sp_path[k][j];
                }

                // 将新顶点加入路径
                arr_sp_path[i][j] = i;
                // 在后面加上一个-1表终止
                if(j + 1 < n) arr_sp_path[i][j+1] = -1;

            }
        }

        // 记录结果并将 k 标记为已找到
        arr_sp[k] = 0;
    }

    return arr_sp_path;
}

辅助函数:

// Dijkstra获得最短路径的值
/*
g:邻接矩阵
sp_path:
*/
int* graph_shortestPath_Dijkstra_getValue(int** g, int** sp_path, int n)
{
    int* sp_value = utils_createArr(n, 0);
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0; j < n-1; j ++)
        {
            // 如果不可达,赋值为-1
            if(sp_path[i][0] == -1)
            {
                sp_value[i] = -1;
                break;
            }

            // 到最后一个路径结点
            if(sp_path[i][j+1] == -1) break;

            // 加上边的代价
            sp_value[i] += g[sp_path[i][j]][sp_path[i][j+1]];
        }
    }

    return sp_value;
}

样例展示:
在这里插入图片描述

测试代码:

int main()
{
    int arr[21] = {0,1,10,0,3,30,0,4,100,1,2,50,2,4,10,3,2,20,3,4,60};
    int n = 5; // 结点个数
    int** g = graph_createArrInValue(n, -1);
    graph_createAdjacencyMatrixByArrWithWeight(g,arr, 21, 1);

    printf("原图邻接矩阵:\n");
    graph_print(g, n, "%3d ");
    printf("\n");
    int** sp_path = graph_shortestPath_Dijkstra(g, n,0);

    printf("最短路径:\n");
    utils_print_2DArr(sp_path,n, n, "%3d");

    // 输出最短路径的值
    utils_print_arr(
        graph_shortestPath_Dijkstra_getValue(g,sp_path,n),
        0,n-1,"%3d","最短路径值:\n", "\n");

    return 0;
}

运行结果:
在这里插入图片描述

参数变化图表:
在这里插入图片描述

Floyd算法

Floyd算法用于每一对顶点之间的最短路径问题,问题描述如下:给定带权有向图 G = ( V , E ) G=(V,E) G=(V,E) ,对任意顶点 v i v_i vi 到顶点 v j ( i ≠ j ) v_j(i \not= j) vj(i=j),求顶点 v i v_i vi 到顶点 v j v_j vj 的最短路径。

解决这个问题的一个办法是:每次以一个顶点为源点,调用 Dijkstra 算法 n 次,便可求得每一对顶点之间得最短路径,显然,时间复杂度为 O ( n 3 ) O(n^3) O(n3) 。弗洛伊德提出了求每一对顶点之间的最短路径算法——Floyd算法,其时间复杂度也是 O ( n 3 ) O(n^3) O(n3) ,但形式上要简单一些。

Floyd算法的基本思想:假设从 v i v_i vi v j v_j vj 的弧(若从 v i v_i vi v j v_j vj 的弧不存在,则将弧的权值看作 $\infin $ )是最短路径,然后进行 n 次试探。首先比较 v i , v j v_i,v_j vi,vj v i , v 0 , v j v_i,v_0,v_j vi,v0,vj 的路径长度,取长度较短者作为从 v i v_i vi v j v_j vj 中间顶点的编号不大于0的最短路径。在路径上再增加一个帝国点 v 1 v_1 v1 v i , . . . , v 1 , . . . , v j v_i,...,v_1,...,v_j vi,...,v1,...,vj 和已经得到的从 v i v_i vi v j v_j vj 中间顶点的路径。以此类推,在一般情况下,若 v i , . . . , v k v_i,...,v_k vi,...,vk v k , . . . , v j v_k,...,v_j vk,...,vj 分别是从 v i v_i vi v k v_k vk 和 从 v k v_k vk v j v_j vj 的中间顶点编号不大于 k − 1 k-1 k1 的最短路径,则将 v i , . . . , v k , . . . , v j v_i,...,v_k,...,v_j vi,...,vk,...,vj 和已经得到的从 v i v_i vi v j v_j vj 中间结点的编号不大于 k − 1 k-1 k1 的最短路径相比较,取长度较短者为从 v i v_i vi v j v_j vj 中间顶点不大于 k k k 的最短路径。经过 n n n 次比较后,最后求得的必是从 v i v_i vi v j v_j vj 的最短路径。

就是假设有 k k k 个顶点,编号为 0 ∼ k − 1 0\sim k-1 0k1,以邻接矩阵为初始矩阵(到自身的路径要设为0),从编号为0的结点开始,一直到编号为 k − 1 k-1 k1 的结点。设加入的结点编号为 k k k,要计算最短路径的结点为 i i i j j j ,若存在 i → j < i → k + k → j i \rightarrow j < i \rightarrow k + k \rightarrow j ij<ik+kj,则替换,否则不管。当把所有结点都加入的时候最短路径就计算完成了。

代码实现:

// Floyd最短路径
/*
g:图的邻接矩阵
n:顶点个数

return:每个点的邻接矩阵
*/
int** graph_shortestPath_Floyd(int** g, int n)
{
    // 创建二维数组存储弗洛伊德最短路径
    int** sp_Floyd = utils_create2DArr(n,n,-1);

    // 初始化最短路径矩阵
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0; j < n; j ++)
        {
            // 到自身距离设为 0
            if(i == j) sp_Floyd[i][j] = 0;
            else sp_Floyd[i][j] = g[i][j];
        }
    }

    for(int k = 0; k < n; k ++)
    {
        // 每次加入编号为 k 的结点
        for(int i = 0; i < n; i ++)
        {
            for(int j = 0; j < n; j ++)
            {
                // 当跟 k 没有路径时,跳过
                if(sp_Floyd[i][k] == -1 || sp_Floyd[k][j] == -1) continue;

                // 当不存在路径或当前最短路径大于新路径时,替换
                if(sp_Floyd[i][j] == -1 || 
                   sp_Floyd[i][j] > sp_Floyd[i][k] + sp_Floyd[k][j])
                {
                    sp_Floyd[i][j] = sp_Floyd[i][k] + sp_Floyd[k][j];
                }
            }
        }
    }

    return sp_Floyd;
}

测试样例:

在这里插入图片描述

测试代码:

int main()
{
    int arr[15] = {0,1,4,0,2,11,1,0,6,1,2,2,2,0,3};
    int n = 3; // 结点个数
    int** g = graph_createArrInValue(n, -1);
    graph_createAdjacencyMatrixByArrWithWeight(g,arr, 15, 1);

    printf("原图邻接矩阵:\n");
    graph_print(g, n, "%3d ");
    printf("\n");
    int** sp_path = graph_shortestPath_Floyd(g, n);

    printf("最短路径:\n");
    utils_print_2DArr(sp_path,n, n, "%3d");

    return 0;
}

运行结果:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值