【算法总结】图论-最短路径

【算法总结】图论-最短路径

一、概念

最短路径问题。即寻找图中某两个特定结点间最短的路径长度。所谓图上的路径,即从图中一个起始结点到一个终止结点途中经过的所有结点序列,路径的长度即所经过的边权和。

二、Floyd算法

用邻接矩阵保存原图,那么此时邻接矩阵中 edge[i][j]的值即表示从结点 i 到 结点j,中间不经过任何结点时距离的最小值(若它们之间有多条边,取最小权值保存至邻接矩阵;也可能为无穷,即不可达)。假设结点编号为 1 到 N,我们再考虑从结点i 到结点j中间只能经过编号小于等于1的结点(也可以不经过)时最短路径长度。与原始状况相比,在中间路径上可以经过的结点增加了编号为1 的结点。我们又知道,最短路径上的结点一定不会出现重复(不考虑存在负权值的情况)。那么,某两个结点间若由于允许经过结点 1 而出现了新的最短路径, 则该路径被结点 1 分割成两部分:由 i 到结点 1,同时中间路径上不经过结点 1 的第一段路径;由结点 1 到 j,中间路径上同样不经过结点 1 的第二段路径,其路径总长度为edge[i][1] + edge[1][j]。要确定该路径是否比不允许经过结点1时更短,我们比较edge[i][1] + edge[1][j]与edge[i][j]之间的大小关系。若前者较小, 则说明中间路径经过结点1时比原来更短,则用该值代表由i 到j 中间路径结点 编号小于等于1的最短路径长度;否则,该路径长度将依然保持原值edge[i][j], 即虽然允许经过结点1,但是不经过时路径长度最短。

考虑更一般的情况,若edge[i][j]表示从结点i到结点j,中间只能经过编号小于k的点时的最短路径长度,我们可以由这些值确定当中间允许经过编号小于等 于k的结点时,它们之间的最短路径长度。同样,与原情况相比,新情况中允许出现在中间路径的结点新增了编号为 k 的结点,同理我们确定 edge[i][k] + edge[k][j]的值与edge[i][j]的值,若前者较小则该值代表了新情况中从结点i到结 点j的最短路径长度;否则,新情况中该路径长度依旧保持不变。

如上文所说,在图的邻接矩阵表示法中,edge[i][j]表示由结点i到结点j中间 不经过任何结点时的最短距离,那么我们依次为中间允许经过的结点添加结点 1、结点 2、……直到结点N,当添加完这些结点后,从结点i到结点j允许经过所有结点的最短路径长度就可以确定了,该长度即为原图上由结点 i 到结点 j 的 最短路径长度。

我们设ans[k][i][j]为从结点i到结点j允许经过编号小于等于k的结点时其最短路径长度。如上文,ans[0][i][j]即等于图的邻接矩阵表示中 edge[i][j]的值。我们通过如下循环,完成所有k对应的ans[k][i][j]值的求解:

for (int k = 1; k <= n; k++)//从1至n循环k
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)//遍历所有的i,j
        {
            if (ans[k - 1][i][k] == 无穷 || anx[k - 1][k][j] == 无穷)//当允许经过前k-1个结点时,i,j不能与k联通,则ij之间目前为止不存在经过k的路径
            {
                ans[k][i][j] = ans[k - 1][i][j];//保持原值,即从i到j经过前k个点和允许经过前k-1个结点时最短路径长度相同
                continue;
            }
            if (ans[k - 1][i][j] == 无穷 || anx[k - 1][i][k] + ans[k - 1][k][j] < ans[k - 1][i][j])ans[k][i][j] = anx[k - 1][i][k] + ans[k - 1][k][j];//经过前k-1个结点,i,j不连通或者经过结点k可以得到更短的路径,则更新最短值
            else ans[k][i][j] = ans[k - 1][i][j];
        }
    }
}

经过这样的n次循环后,我们即可得到所有结点间允许经过所有结点条件下的最短路径长度,该路径长度即为我们要求的最短路径长度。即若要求得 ab 之间的最短路径长度,其答案为ans[n][a][b]的值。

同时我们注意到,我们在通过ans[k - 1][i][j]的各值来递推求得ans[k][i][j]的值时,所有的ans[k][i][j]值将由ans[k - 1][i][j]和ans[k - 1][i][k] + ans[k - 1][k][j]的大小关系确定,但同时ans[k][i][k]和ans[k][k][j]必定与ans[k - 1][i][k]和ans[k - 1][k][j]的值相同,即这些值不会因为本次更新而发生改变。所以我们将如上代码片段简化成如下形式:

for (int k = 1; k <= n; k++)//从1至n循环k
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)//遍历所有的i,j
        {
            if (ans[i][k] == 无穷 || anx[k][j] == 无穷)continue;
            if (ans[i][j] == 无穷 || anx[i][k] + ans[k][j] < ans[i][j])ans[i][j] = anx[i][k] + ans[k][j];
        }
    }
}

如该代码片段所示,我们将原本的三维数组简化为二维数组,而每次更新时直接在该二维数组上进行更新。这是有原因的,当最外层循环由k - 1变为k时, 各 ans[i][k]和 ans[k][j]的值不会因为本次更新发生改变(当前 i 到 k 的最短路径中途必不经过结点k),而本次更新又是由它们的值和各ans[i][j]的值比较而进行 的。所以我们直接在二维数组上进行本次更新,并不会影响到本次更新中其它各值的判定。节省了大量的内存空间,同时还省略了保持原值的操作。

例 5.5 最短路 

解题思路

本例是最简单的最短路问题了。我们首先分析复杂度,如我们在上文中给出的代码所示,floyd算法主要包括一个三重循环,每重循环的循环次数均是N,这样 Floyd算法的时间复杂度为O(N^3),空间复杂度为O(N^2),其中N均为图中结点的个数。

在本例中N最大值为100,N^3的时间复杂度尚在我们可以接受的范围内。

AC代码

#include<cstdio>

int ans[101][101];//二维数组,其初始值即为该图的邻接矩阵

int main()
{
    int n, m;
    while (scanf("%d%d", &n, &m) != EOF)
    {
        if (n == 0 && m == 0)break;
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                ans[i][j] = -1;//初始化邻接矩阵,用-1代表无穷
            }
            ans[i][i] = 0;//初始化,自己到自己的路径长度为0
        }
        while (m--)//读入道路
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            ans[a][b] = ans[b][a] = c;//无向图,赋值两次
        }
        for (int k = 1; k <= n; k++)
        {
            for (int i = 1; i <= n; i++)
            {
                for (int j = 1; j <= n; j++)
                {
                    if (ans[i][k] == -1 || ans[k][j] == -1)continue;
                    if (ans[i][j] == -1 || ans[i][k] + ans[k][j] < ans[i][j])ans[i][j] = ans[i][k] + ans[k][j];
                }
            }
        }
        printf("%d\n", ans[1][n]);
    }
    return 0;
}

AC代码

原文地址:https://www.cnblogs.com/yun-an/p/11089120.html

时间: 2024-10-03 17:50:12

【算法总结】图论-最短路径的相关文章

玩转算法系列--图论精讲 面试升职必备(Java版)

第1章 和bobo老师一起,玩转图论算法欢迎大家来到我的新课程:<玩转图论算法>.在这个课程中,我们将一起完整学习图论领域的经典算法,培养大家的图论建模能力.通过这个课程的学习,你将能够真正地,玩转图论算法:) 第2章 图的基本表示千里之行,驶于足下.解决任何有一个图论算法问题,首先需要用基本的数据结构来表示图.在这一章,我们就将探索图的基本表示问题,学习邻接矩阵和邻接表,进而,也让同学们熟悉这个课程的整体代码风格. 第3章 图的深度优先遍历任何一种数据结构,都需要进行遍历.图也不例外.通过深

编程算法 - 迷宫的最短路径 代码(C++)

迷宫的最短路径 代码(C++) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 给定一个大小为N*M的迷宫. 迷宫由通道和墙壁组成, 每一步可以向邻接的上下左右四格的通道移动. 请求出从起点到终点所需的最小步数. 请注意, 本题假定从起点一定可以移动到终点. 使用宽度优先搜索算法(DFS), 依次遍历迷宫的四个方向, 当有可以走且未走过的方向时, 移动并且步数加一. 时间复杂度取决于迷宫的状态数, O(4*M*N)=O(M*N). 代码: /* * m

算法8-10:最短路径算法之拓扑排序

该算法的基本思想就是按照拓扑排序的顺序依次将每个顶点加入到最短路径树中,每次加入时将该顶点延伸出的所有顶点进行"放松"操作.这种算法的复杂度是E+V. 代码 这种算法的代码比Dijkstra还要简单,代码如下: public class TopologySP extends SP { public TopologySP(EdgeWeightedDigraph G, int s) { super(G, s); // 将所有顶点到原点的距离设为无穷大 // 注意:下面这段代码不要遗漏 fo

算法8-11:最短路径算法之负权

负权指的是一张图中包含一条权重小于0的边.负环指的是一张图中存在权重只和为负数的环.如果一张图中存在负环,那么这张图是没有最短路径的. 那么,假设图中不存在负环,但是有负权,那么最短路径如何求解呢?答案就是使用Bellman-Ford算法,该算法的性能一般. 基本思想 Bellman-Ford算法的基本思想就是对图中所有的边都进行V次"放松"操作.所以该算法的复杂度是E×V. 该算法的计算过程如下图所示. 改进 实际应用中会对算法进行改进.改进方法就是用队列记录发生变化的顶点,这样能减

算法8-7:最短路径接口

最短路径问题就是给定一个图,这个图中的边是有方向和权重的.求s到t的最短路径. 最短路径问题其实分为很多种.按照起点和终点来分,可以分为: 从一个顶点到另一个顶点 从一个顶点到其他所有顶点 从所有顶点到所有顶点 按照边的权重来分可以分为: 非负权 任意权 欧几里德权 按照是否有环可以分为 无环最短路径 无负环最短路径 类的定义 在实现最短路径算法之前,需要先在程序中定义有向权重图. 有向权重边的定义如下: public class DirectedEdge { private int v; pr

A星算法,找寻最短路径

#include <iostream> #include <cstdio> #include <queue> #include <algorithm> #include <cmath> #define N 1000 #define inf 1<<30; using namespace std; /* a星算法,找寻最短路径 算法核心:有两个表open表和close表 将方块添加到open列表中,该列表有最小的和值.且将这个方块称为S吧

hdu 2680 最短路径(dijkstra算法+多源最短路径单源化求最小值)

Choose the best route Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 7062    Accepted Submission(s): 2301 Problem Description One day , Kiki wants to visit one of her friends. As she is liable

数据结构与算法--单源最短路径算法之dijkstra

单源最短路径之dijkstra算法 最优子问题:dis(s,...,e)是s到e的最短路径,在这条路径上的所有点之间dis(pi,pj)距离是最小的. 算法思路: 首先初始化,dis[s][i]是s到i的距离,直接相连的就是其距离,不直接相连的就是无穷大 下面是算法主要模块: 1.选取dis[i]最小的点加入到P{S}中, 2.计算是否更新dis[j],j是和i直接相连的 3.重复以上步骤,直到e

算法8-8:最短路径性质

在计算最短路径之前,往往会先计算最短路径树.也就是计算从一个顶点出发,到其余全部顶点的最短距离. 有了最短路径树之后.路径和距离就很easy实现了: public double distTo(int v) { return distTo[v]; } public Iterable<DirectedEdge> pathTo(int v) { Stack<DirectedEdge> result = new Stack<DirectedEdge>(); DirectedEd