最短路径算法 dijkstra bellman-ford floyd

2024-05-13 02:38

本文主要是介绍最短路径算法 dijkstra bellman-ford floyd,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Dijkstra算法:

1.定义概览

Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。注意该算法要求图中不存在负权边。

问题描述:在无向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短路径。(单源最短路径)

 

2.算法描述

1)算法思想:设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。

2)算法步骤:

a.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。

b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。

c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。

d.重复步骤b和c直到所有顶点都包含在S中。

3)算法原理:

算法原理可以用非常简单的反证法进行证明:若点p -> q 存在最短路径为p->a->q,则p到a的最短路径必定为p->a。若存在更短的路径p->b->a,则p到q的最短路径则为p->b->a->q,与题设矛盾。故可以得出以下这个结论,最短路径上处处都是最短路径。所以可以证明迪杰斯特拉算法是正确的。

4)算法适合范围:

算法仅适合求正权上的最短路径,也可以进行一些变型计算,不过尚未能证明,但是过了oj。例如:POJ2253-Frogger

5)算法实现

const int  MAXINT = 32767;
const int MAXNUM = 10;
int dist[MAXNUM];
int prev[MAXNUM];int A[MAXUNM][MAXNUM];void Dijkstra(int v0)
{bool S[MAXNUM];                                  // 判断是否已存入该点到S集合中int n=MAXNUM;for(int i=1; i<=n; ++i){dist[i] = A[v0][i];S[i] = false;                                // 初始都未用过该点if(dist[i] == MAXINT)    prev[i] = -1;else prev[i] = v0;}dist[v0] = 0;S[v0] = true;   for(int i=2; i<=n; i++){int mindist = MAXINT;int u = v0;                               // 找出当前未使用的点j的dist[j]最小值for(int j=1; j<=n; ++j)if((!S[j]) && dist[j]<mindist){u = j;                             // u保存当前邻接点中距离最小的点的号码 mindist = dist[j];}S[u] = true; for(int j=1; j<=n; j++)if((!S[j]) && A[u][j]<MAXINT){if(dist[u] + A[u][j] < dist[j])     //在通过新加入的u点路径找到离v0点更短的路径  {dist[j] = dist[u] + A[u][j];    //更新dist prev[j] = u;                    //记录前驱顶点 }}}
}

bellman-ford算法:

1.定义概览

Bellman-ford算法是求含负权图的单源最短路径算法,效率很低,但代码很容易写。即进行持续地松弛(原文是这么写的,为什么要叫松弛,争议很大),每次松弛把每条边都更新一下,若n-1次松弛后还能更新,则说明图中有负环,无法得出结果,否则就成功完成。

2.算法描述

1)算法思想

Bellman-Ford算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于给定的带权(有向或无向)图 G=(V,E),其源点为s,加权函数 w是 边集 E 的映射。对图G运行Bellman-Ford算法的结果是一个布尔值,表明图中是否存在着一个从源点s可达的负权回路。若不存在这样的回路,算法将给出从源点s到 图G的任意顶点v的最短路径d[v]。

2)算法步骤

a.初始化:将除源点外的所有顶点的最短距离估计值 d[v] ←+∞, d[s] ←0;

b.迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次)

c.检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在 d[v]中。

Tips:解释一下松弛操作是什么意思,松弛操作就是指不断迭代比较某点到某点的权或者称为代价,若点a的权为d[a],点 b的权为d[b], 从a->b的权值为 w[a][b] ,若 d[a]+ w[a][b]<d[b] ,则 d[b]=d[a]+ w[a][b] 。 这个不断更新迭代的过程就叫做松弛。其实本人觉得叫紧绷也无不可。

3)算法原理

这个算法原理就比较抽象:可以想象成拥有层级关系的叶子。第一次遍历是从源点出发,找出只通过1个点的最短路径,相当于是第一层,第二次遍历是找出只通过2个点的最短路径,相当于是第二层。最终,找出通过n个点的最短路径。一层找到一个最短路径,和一个点,所以要循环n-1次。而且每次遍历每一条边。

4)算法适合范围

算法特别适合找出图中的负环这一个特性,代表题目有:poj1860、poj3259。 这是属于算法的专有特性。不要忘记咯。

5)算法实现

#include<iostream>#include<cstdio>using namespace std;#define MAX 0x3f3f3f3f#define N 1010int nodenum, edgenum, original; //点,边,起点typedef struct Edge //边
{int u, v;int cost;
}Edge;Edge edge[N];
int dis[N], pre[N];bool Bellman_Ford()
{for(int i = 1; i <= nodenum; ++i) //初始化dis[i] = (i == original ? 0 : MAX);for(int i = 1; i <= nodenum - 1; ++i)for(int j = 1; j <= edgenum; ++j)if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //松弛(顺序一定不能反~){dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;pre[edge[j].v] = edge[j].u;}bool flag = 1; //判断是否含有负权回路for(int i = 1; i <= edgenum; ++i)if(dis[edge[i].v] > dis[edge[i].u] + edge[i].cost){flag = 0;break;}return flag;
}void print_path(int root) //打印最短路的路径(反向)
{while(root != pre[root]) //前驱{printf("%d-->", root);root = pre[root];}if(root == pre[root])printf("%d\n", root);
}int main()
{scanf("%d%d%d", &nodenum, &edgenum, &original);pre[original] = original;for(int i = 1; i <= edgenum; ++i){scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].cost);}if(Bellman_Ford())for(int i = 1; i <= nodenum; ++i) //每个点最短路{printf("%d\n", dis[i]);printf("Path:");print_path(i);}elseprintf("have negative circle\n");return 0;
}

Floyd算法:

1)算法思想原理

Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)

从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。

2).算法描述

a.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。   

b.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。

3)算法证明

个人感觉这个算法就像是Bellman-Ford算法的升级版,只不过从单源变成了可以求多源,双层循环变成了三层循环。可以看成是,对于每一个节点,都不断遍历成对节点的所有可能性,有没有可能从这个节点是中间节点的情况会更近呢,有点话就更新,没有就拉倒,你从那里到这里就可以不从这里过。

4)算法适合范围

这个算法适合利用这种拆分思想,例如完美实现了这一拆分思想的POJ2253-Frogger,利用多源点思想的poj2240。

5)算法实现

typedef struct          
{        char vertex[VertexNum];                                //顶点表         int edges[VertexNum][VertexNum];                       //邻接矩阵,可看做边表         int n,e;                                               //图中当前的顶点数和边数         
}MGraph; void Floyd(MGraph g)
{int A[MAXV][MAXV];int path[MAXV][MAXV];int i,j,k,n=g.n;for(i=0;i<n;i++)for(j=0;j<n;j++){   A[i][j]=g.edges[i][j];path[i][j]=-1;}for(k=0;k<n;k++){ for(i=0;i<n;i++)for(j=0;j<n;j++)if(A[i][j]>(A[i][k]+A[k][j])){A[i][j]=A[i][k]+A[k][j];path[i][j]=k;} } 
}









这篇关于最短路径算法 dijkstra bellman-ford floyd的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/984459

相关文章

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

Linux修改pip和conda缓存路径的几种方法

《Linux修改pip和conda缓存路径的几种方法》在Python生态中,pip和conda是两种常见的软件包管理工具,它们在安装、更新和卸载软件包时都会使用缓存来提高效率,适当地修改它们的缓存路径... 目录一、pip 和 conda 的缓存机制1. pip 的缓存机制默认缓存路径2. conda 的缓

Windows系统下如何查找JDK的安装路径

《Windows系统下如何查找JDK的安装路径》:本文主要介绍Windows系统下如何查找JDK的安装路径,文中介绍了三种方法,分别是通过命令行检查、使用verbose选项查找jre目录、以及查看... 目录一、确认是否安装了JDK二、查找路径三、另外一种方式如果很久之前安装了JDK,或者在别人的电脑上,想

Python中Windows和macOS文件路径格式不一致的解决方法

《Python中Windows和macOS文件路径格式不一致的解决方法》在Python中,Windows和macOS的文件路径字符串格式不一致主要体现在路径分隔符上,这种差异可能导致跨平台代码在处理文... 目录方法 1:使用 os.path 模块方法 2:使用 pathlib 模块(推荐)方法 3:统一使

一文教你解决Python不支持中文路径的问题

《一文教你解决Python不支持中文路径的问题》Python是一种广泛使用的高级编程语言,然而在处理包含中文字符的文件路径时,Python有时会表现出一些不友好的行为,下面小编就来为大家介绍一下具体的... 目录问题背景解决方案1. 设置正确的文件编码2. 使用pathlib模块3. 转换路径为Unicod

如何通过Golang的container/list实现LRU缓存算法

《如何通过Golang的container/list实现LRU缓存算法》文章介绍了Go语言中container/list包实现的双向链表,并探讨了如何使用链表实现LRU缓存,LRU缓存通过维护一个双向... 目录力扣:146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2.

MySQL9.0默认路径安装下重置root密码

《MySQL9.0默认路径安装下重置root密码》本文主要介绍了MySQL9.0默认路径安装下重置root密码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录问题描述环境描述解决方法正常模式下修改密码报错原因问题描述mysqlChina编程采用默认安装路径,

golang字符串匹配算法解读

《golang字符串匹配算法解读》文章介绍了字符串匹配算法的原理,特别是Knuth-Morris-Pratt(KMP)算法,该算法通过构建模式串的前缀表来减少匹配时的不必要的字符比较,从而提高效率,在... 目录简介KMP实现代码总结简介字符串匹配算法主要用于在一个较长的文本串中查找一个较短的字符串(称为

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1