最短路小结(三种算法+各种常见变种)

额,博主只是做了几(约数)道题而已,写这篇小结纯粹想留作纪念(勿喷,但是可以交流)(那啥,转载的话注明一下来源。。打字不容易。。)
最短路呢,包括三种算法,但是各有各的变种,其中变化有很多。
简单记录一下。
首先是三种算法:
1、Dijkstra算法。(单源点最短路径)
双手奉上啊哈雷算法
然后开始叙述我所理解(如有雷同。。那就雷同吧)的:
有n个点,相互之间可能有所连接或者没有,那么现在,如果让求点1到点n之间的最短路,该怎么求?
这里面有个很有趣的东西,叫做松弛,翻译过来就是这么个意思:
假如已知最短路1-3长度为5,最短路1->4长度为7(无论是1-3,还是1-4均是当前最短,也可以理解成当前最优(dp)),而3->4的长度为1,那么这个时候,明显可以知道:1->3->4的长度是小于1->4的,那么现在把1-4更新到当前最优,长度也就是变成了6。
以上就是松弛操作。也就是假设在1-n中有一个x点,且用d数组表示1~其他点的当前最优最短路,用w二维数组表示两点之间的距离,那么给定一个当前最优点y,如果d[y]>d[x]+w[x][y],是不是说明d[y]可以有更小的值呢?答案显而易见。
那么这是一个点,题目中一共n个点,那么我用每一个点都去更新1到其他点的距离,那么全部更新过后,是不是d[n]表示的就是从1~n的最短路了呢?
而这一切,每一次更细都相当于在所有任意的两个点(其中一个点是1(起点))里插入第三个点,看是否能够松弛,若能,就松弛,不能,就不管了呗。
还有,既然,n个点都要去更新一遍,那么谁先去更新呢?
答案:当然是d值最小的那个点去更新,因为用最小的去更新,才可能更新的动呀。但是既然,只需要每个点更新一次,那么要做个标记,以防更新过的点再次进行更新。
d值会越来越小,那么初值肯定是越大越好咯。
但是呢,既然d数组代表的是1到其余各点的最短距离,那么d[1]肯定是0。。
例题博客(基本上我第一次写的题博客都很长。。):Til the Cows Come Home
来啊,代码伺候!!!

int INF=0x3f3f3f3f
for(int i=1;i<=n;i++)
{
    d[i]=INF;
    vis[i]=0;
}
d[1]=0;
for(int i=1;i<=n;i++)//每个点都要参与更新,所以循环n次
{
    int m=INF,x=-1;//m是为了找出最小那一个,x记录节点
    for(int j=1;j<=n;j++)
    {
        if(!vis[j]&&d[j]<m)
        {
            m=d[x=j];
        }
    }
    if(x!=-1)
    {
        vis[x]=1;//用于更新过了,就要标记
        for(int j=1;j<=n;j++)//用最小的d[x]去更新d[j]
        {
            if(d[j]>d[x]+w[x][j])
            {
                d[j]=d[x]+w[x][j];
            }
        }
    }
}

2、bellman(附加spfa)
既然已经知道什么是松弛(不知道的再看一遍,因为是环环相扣的),接下来就说一下什么是bellman。
刚才已经说了,Dijkstra算法是用n个点去更新从1点到其余各点(这里的1代表起点,谁是起点看题意,只需要把起点的d变成0就可以了)的最短路,那么也就是用的边去更新两点之间的距离。
然后就提出了bellman,思想是从源点逐次经过其他点,以缩短到达终点的距离,假设n个点不存在负权值回路,那么最多存在n-1条边,因为假设存在超过n-1条边,那么肯定会重复经过一个点,那么最短路就可以更新:
Bellman-Ford算法构造一个最短路径长度数组序列:dist(1)[u],dist(2)[u],dist(3)[u],…,dist(n-1)[u]。其中:
dist(1)[u]为从源点v0到终点u的只经过一条边的最短路径的长度,并有dist(1)[u]=edge[v0,u]。
dist(3)[u]为从源点v0出发最多经过不构成负权值回路的3条边到达终点u的最短路径长度。
……
dist(n-1)[u]为从源点v0出发最多经过不构成负权值回路的n-1条边到达终点u的最短路径长度。
算法的最终目的是计算出dist(n-1)[u],为源点v0到顶点u的最短路径长度,也就是利用n-1条边更新过后的最短距离。
采用递推方式计算dist(k)[u]。
设已经求出dist(k-1)[u],u=0,1,…,n-1,此即从源点v0最多经过不构成负权值回路的k-1条边到达终点u的最短路径的长度。
从图的邻接矩阵可以找出各个顶点j到达顶点u的(直接边)距离edge[j,u],计算min{distk-1[j]+edge[j,u]},可得从源点v0途经各个顶点,最多经过不构成回路的k条边到达终点u的最短路径的长度。 比较dist(k-1)[u]和min{dist(k-1)[j]+edge[j,u]},取较小者作为dist(k)[u]的值。
因此Bellman-Ford算法的递推公式(求源点v0到各顶点u的最短路径)为: 初始:dist(1)[u]=edge[v0,u],v0是源点 递推:dist(k)[u]=min{dist(k-1)[u],min{dist(k-1)[j]+edge[j,u]}} j=0,1,…,n-1,j<>u; k=2,3,4,…,n-1 。
所以,n-1次过后便可以得到最短路,如果n-1次后依旧可以更新,那么说明存在负权回路或者是正权回路。
其次,对于无论是Dijkstra还是Bellman,均有一个选点去更新的操作,那么有的时候,可能选出的点不能对任何点进行更新,所以造成了时间的浪费,而且,还有一句是被更新过的点一定可以去更新其他点(不知道对不对。。),然后呢,就会想到,为什么不把每次更新过的点装进一个容器呢?直到再没有点可以装进容器(代表最短路已是最优,更新完毕)。
然后想到了数组,想到了栈和队列,但是呢,数组模拟需要一数组还要一指针变量时刻维护,所以就用栈和队列吧,相同的时间复杂度,但是呢,还有一点,选出尽量小的去更新其他点,这样更好。所以,考虑优先队列,那为啥不考虑优先。。栈呢?因为意义一样呀。。。
回到正,负权的问题上,怎样进行初始化呢、、
对于负权,自然是越来越小,所以数组赋为极大值,正权的话,赋为0好了。
给出一篇判定负环的博客:Wormholes
给出一篇判定正环的博客:Arbitrage,内含两种判定正环的方法,一个是bellman,一个是floyed(下文有)。
给出一篇使用spfa的题解(模板):Til the Cows Come Home
然后,奉上优先队列(经典,当然不是我的。。)博客:优先队列详解
以上就是bellman和spfa用法,对了,提到spfa就不得不提到差分约束系统,自行看下(尝试理解,因矩阵知识浅薄,所以只会最基础的):Candies,里面有差分约束的博客,但是最好还是先看下题,理解这种思想用于哪个方面。


3、floyed
floyed是求任意两点间的最短路算法,因为三个for让他拥有不小的局限性,若是1s的话,n的范围只能是100以下,若是bellman能够理解成为利用n-1条边去更新起点到定点的最短路,记录的是前i条边更新过后的状态,那么floyed就可以理解为利用点去更新,记录的是前i个点更新过后的当前最优状态,三个for,假如分别是k,i,j,那么每次都会利用k作为中间桥梁,询问,i->j,i->k->j这两个哪个更短。。。保留当前最优,直到利用所有点把这对i,j更新n遍。(也就是n遍Dijkstra),这样理解会不会容易一些。
然后呢,floyed会牵扯出一个问题,叫做传递闭包问题,也就是关系的一个转换而已,给出一道例题解析:Cow Contest,这种题数据小的话(<=100)直接floyed暴力过,大了的话,就又牵扯出一个概念,叫做强连通分量,又有三种算法(暂不解释)。


以上呢,就是三种算法,在这里,补充一点Dijkstra的变种,其实也不算是变种,只是松弛操作的内容有点区别而已,都是共通的。
其一呢:最小生成树之prim算法(补了一觉继续写。。)。
现在我不再区别最短路的三种算法,统称为最短路。
那么思考最短路与最小生成树的区别,一样是求的最小值,但是最短路两点之间的最短路,而最小生成树求得是将全图的点连起需要的最小长度。
给出百度百科解释(很容易理解):prim算法百度百科,每次都找已经找过的点的最小距离,那么利用代码怎么实现呢?
找到离部分连通图最近的d值的点,加入标记,然后用这条边去更新所有没有被标记的点的距离。
附代码:

void prim()
{
    for(int i=1;i<=n;i++)
        d[i]=w[1][i];//初始化,也可以全设为INF
    vis[1]=1;
    int sum=0;
    for(int i=2;i<=n;i++)
    {
        int minn=INF,pos=0;
        for(int j=1;j<=n;j++)//选出一个最近的点
        {
            if(!vis[j]&&d[j]<minn)
            {
                minn=d[pos=j];
            }
        }
        sum+=minn;//求最小距离
        vis[pos]=1;
        for(int j=1;j<=n;j++)//更新没有被标记的点
            if(!vis[j]&&d[j]>w[pos][j])
                d[j]=w[pos][j];
    }
}

d[j]>w[pos][j]是指:因为只要把全图连接起来便好,所以不用像最短路那样严格控制是一条路。并且,d数组的用法在变种里面都代表不同的含义,因为之前在写题解的时候写过了,所以直接给出那道题(内含d数组的讲解)Frogger,然后给出一道模板题:Jungle Roads
其二呢,就是刚才给出的那道Frogger,这类题的基本题意是最一个有向或者无向图里,从一个起点(假设为st)到一个重点(假设为ed)有很多条路,那么,每一条路呢都有一个最大长度边和一个最小长度边,那么这类题就会拿这个做文章,问:从st到ed里所有路的最小边长的最大值是多少?或者是从st到ed里所有路的最大边长的最小值是多少?
而这些呢,主要就是d数组的差异,给出两道题,分别对应两种问法。
Frogger
Heavy Transportation
那么整理到这里,也就是我花了那么多天的做的专题的成果了。
参考:floyed算法bellman算法
对了对了,再补充一个邻接表,双手奉上啊哈雷大大的邻接表博客,然后,就是自己的补充了:

假设一个有向图,存在n个点,m条边
那么:
我见过的有两种方式:
①
struct djh
{
    int u,v,w;代表含义分别是:左端点,右端点,边长
}edge[];这个数组的范围是按照m的范围而定的
int first[]这个数组的范围是按照n的范围而定的
int next[]这个数组的范围是按照m的范围而定的
初始化:
for(int i=1;i<=n;i++)
{
    first[i]=-1;
}
for(int i=1;i<=m;i++)
{
    next[i]=-1;
}
输入边的时候:
for(int i=1;i<=m;i++)
{
    scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
    next[i]=first[edge[i].u];
    first[edge[i].u]=i;
}
dijs或者spfa或者其他引用的写法:
选出一个点;
res
for(int i=first[res];i!=-1;i=next[i])
{
    if(dis[edge[i].v>dis[edge[i].u]+edge[i].w)
    {
        dis[edge[i].v=dis[edge[i].u]+edge[i].w
    }
}

②
struct djh
{
    int to,w,next;分别代表右端点,边长,next数组
}edge[];这个数组的范围是按照m的范围而定的
int first[]这个数组的范围是按照n的范围而定的
初始化不变
输入边的时候:
tot=0;
while(m--)
{
    int u,v,w;
    scanf("%d%d%d",&u,&v,&w);
    edge[tot].nexx=first[u];
    edge[tot].v=v;
    edge[tot].w=w;
    first[u]=tot++;
}
dijs或者spfa或者其他引用的写法:
选出一个点;
res
for(int i=first[res];i!=-1;i=edge[i].nexx)
{
    if(d[edge[i].v]>d[res]+edge[i].w)
    {
        d[edge[i].v]=d[res]+edge[i].w;
    }
}

以上便是个人用法。

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 最短路算法是指在加权图中找到从一个顶点到另一个顶点的最短路径的算法。Matlab中有多种实现最短路算法的方式,其中一种比较常用的是Dijkstra算法。下面是使用Matlab实现Dijkstra算法的示例代码。 假设有一个加权无向图,其中有5个顶点V={1, 2, 3, 4, 5}和8条边E={(1,2,10),(1,3,20),(2,3,30),(2,4,15),(3,4,5),(3,5,25),(4,5,20),(5,1,5)},其中每条边的三个元素分别表示起始顶点、终止顶点和边的权重。现在需要求从顶点1到其他各个顶点的最短路径。 ```matlab % 构建邻接矩阵 n = 5; % 图的顶点数 m = 8; % 图的边数 G = inf(n); % 初始化邻接矩阵 for i = 1:n G(i,i) = 0; % 对角线上的元素为0 end for i = 1:m u = E(i,1); % 边的起始顶点 v = E(i,2); % 边的终止顶点 w = E(i,3); % 边的权重 G(u,v) = w; G(v,u) = w; % 对称矩阵 end % Dijkstra算法最短路径 dist = inf(1,n); % 到各个顶点的距离 dist(1) = 0; % 起始点的距离为0 visited = zeros(1,n); % 标记是否访问过 for i = 1:n-1 % 找到距离起点最近的顶点 min_dist = inf; for j = 1:n if ~visited(j) && dist(j) < min_dist u = j; min_dist = dist(j); end end visited(u) = 1; % 标记已访问 % 更新与u相邻的顶点的距离 for v = 1:n if ~visited(v) && G(u,v) < inf new_dist = dist(u) + G(u,v); if new_dist < dist(v) dist(v) = new_dist; end end end end % 输出最短路径 for i = 1:n fprintf('从1到%d的最短距离为:%d\n', i, dist(i)) end ``` 输出结果为: ``` 从1到1的最短距离为:0 从1到2的最短距离为:10 从1到3的最短距离为:20 从1到4的最短距离为:25 从1到5的最短距离为:5 ``` ### 回答2: 最短路算法是一种用于查找网络中两个节点之间最短路径的方法。在Matlab中,我们可以使用图算法工具箱(Graph Algorithm Toolbox)中的函数来实现最短路算法。 一种常用的最短路算法是Dijkstra算法,它适用于没有负权边的图。在Matlab中,我们可以使用函数dijkstra来计算最短路径。这个函数需要输入一个表示图的邻接矩阵,以及起点和终点的索引。邻接矩阵中,矩阵元素a(i,j)表示节点i到节点j之间的权值,如果节点i和节点j之间没有边,则a(i,j)设为无穷大。 另一种常用的最短路算法是Bellman-Ford算法,它可以处理带有负权边的图。在Matlab中,我们可以使用函数bellmanford来计算最短路径。这个函数需要输入一个表示图的邻接矩阵,以及起点和终点的索引。类似于dijkstra函数中的邻接矩阵,Bellman-Ford算法也将矩阵中的无穷大设为节点之间没有边。 使用Matlab的最短路算法可以帮助我们解决许多实际问题,例如在交通网络中求解最短驾驶路径或计算电力网络中的最短传输路径。同时,我们还可以通过可视化结果来更好地理解网络中节点和边之间的关系。 ### 回答3: 最短路算法是图论中的一个重要算法,用于在图中找到从起点到终点的最短路径。其中,Matlab作为一种强大而灵活的编程语言,常常被用来实现算法的计算和可视化。 在Matlab中,可以使用图论工具箱提供的函数来实现最短路算法。其主要步骤如下: 1. 构建图:首先,需要使用图论工具箱的函数创建一个有向图或无向图,并根据实际需求定义节点和边。可以使用函数`graph()`或`digraph()`来构建图。 2. 定义权重:根据实际情况,需要为图的边指定权重。可以使用函数`addedge()`或`addedge()`为图的每条边添加权重。 3. 寻找最短路径:使用函数`shortestpath()`或`shortestpathtree()`来计算从起点到终点的最短路径。这些函数使用Dijkstra算法或Floyd-Warshall算法进行计算。 4. 可视化结果:使用Matlab的绘图工具,如`plot()`或`plotgraph()`函数,将图和最短路径可视化出来,便于观察和分析结果。 需要注意的是,在使用Matlab实现最短路算法时,可以根据具体需求选择合适的算法和函数,并对算法的输入参数进行适当调整,以达到最佳的计算效果。另外,还可以结合其他的Matlab功能,如处理大规模图的函数、并行计算等,来提高算法的执行效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值