最短路径(Floyd-Warshall、Dijkstra、Bellman-Ford)

        图的遍历,通过算法优雅实现。


        上次使用遍历的方法求得最短路径(图的遍历。-CSDN博客),这样虽然可以解决问题,但还是不够优雅,有一些弊端,时间复杂度和空间复杂度都比较高。本博客主要描述三种求最短路径的办法:Floyd-Warshall、Dijkstra、Bellman-Ford。

        这些算法本质上还是实现了图的遍历,但更加优雅、简便、高效,我认为这就是算法的意义。

        Floyd-Warshall

        Floyd-Warshall可以用来解决多源最短路径判断负权回路问题。核心代码只有五行,比较精简,虽然用到了动态规划的思想,但理解起来不算困难。

        

         我们有如图所示的两个有向图,求1->5的最短路径。每个有向图有三条路径:1->5、1->2->5、1->3->4->5;观察可见,a图中1->3->4->5最短,b图中1->5最短,那么我们如何让计算机知道哪一个路径最短呢?

        首先我们达成一个共识,两点之间的距离,通过引入(插入)n个点,可以使路径缩短,如a->b,我们可以通过引入一个点(a->k->b),引入两个点(a->k1->k2->b)来缩短。

        我们先定义两个点不经过另外任意顶点的路径为初始路径。通过在两点之间插入其它顶点,来达到缩小距离的目的,

        那么我们如何插入呢?我们可以先试一试每个顶点中间插入一个顶点看看有没有缩短,再试试插入两个顶点有没有缩短,一直到插入n个顶点有没有缩短,每一轮都在上一次插入得到的最短路径基础上继续插入,这样插入n次便得到了任意两个顶点之间的最短路径。

        因为我们如图所示,并不是每次插入顶点之后,两点之间的路径都会缩短,如果没有缩短的路径,我们就不需要在他的基础上继续插入了;那么有没有可能插入一个点距离没有缩短,在这个基础上再插入一个点距离就缩短了呢?

        答案是肯定有的,比如如图a,我们只经过3到5,3和5之间没有路,距离无穷大,但加上4之后,总路径就会缩短;那么这种情况有没有考虑呢?肯定也是有的,1->4->5这条路径,其中1->4已经包含了3顶点,其实就是1->3->4->5。我们得到的这条路径,就等于1->3、3->4、4->5这三条最短路径相加的结果。

        这就是动态规划的思想,通过不断优化每两个顶点的间的最短距离来实现任意两点之间的最短距离。

        例题

        

        将本图中,顶点和边的关系,存入邻接矩阵,若两个点之间没有路径,距离为正无穷,点到其本身的路径为0.,我们便得到了如图所示的初始路径map[n+1][n+1](从1开始)。

         通过以上方法来设计本题程序:因为是有向图,我们不仅要找从i到j的路径,还要找j到i的路径,他们的路径都有可能通过插入一个点从而距离缩短:

        只通过e寻找任意两点路径的代码如下:

        for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)if (map[i][j]>map[i][e]+map[e][j])map[i][j]=map[i][e]+map[e][j];

        只通过1:

 

        在此基础上通过2(即只允许通过1和2):

        

         在此基础上通过3(即只允许通过1和2和3):

        在此基础上通过4(即允许通过所有顶点),得到最终结果:

        

        核心代码:

    for (int e = 1; e <= n; e++)for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)if (map[i][j]>map[i][e]+map[e][j])map[i][j]=map[i][e]+map[e][j];

         输入输出部分;

        输入:输入m+1行,第一行输入n和m代表顶点和边,接下来的m行每行包含三个变量x,y,z表示从x到y的距离为z。

4 8

1 2 2

1 3 6

1 4 4

2 3 3

3 1 7

3 4 1

4 1 5

4 3 12

        输出:n*n的邻接矩阵

    0    2    5    4

    9    0    3    4

    6    8    0    1

    5    7   10    0

        完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define inf 0x3f3f3f3fint** init(int n,int m);
void free_output(int **map,int n);int main()
{int n,m;scanf("%d%d",&n,&m);int** map=init(n+1,m); for (int e = 1; e <= n; e++)for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)if (map[i][j]>map[i][e]+map[e][j])map[i][j]=map[i][e]+map[e][j];   free_output(map,n);return 0;
}int** init(int n,int m)
{/*****二维数组动态分配内存和初始化************************ */int **map;map=(int **)malloc(sizeof(int*)*n);//二级指针分配n个一级指针for (int i = 0; i < n; i++)map[i]=(int *)malloc(sizeof(int)*n);//一级指针开辟空间for (int i = 0; i < n; i++)memset(map[i],inf,sizeof(int)*n);//二维数组初始化/******可用map[100][100]={0}代替************************ */for (int i = 0,x=0,y=0,flag; i < m; i++){scanf("%d %d %d",&x,&y,&flag);map[x][y]=flag;}for (int i = 0; i <= n-1; i++)map[i][i]=0;return map;
}void free_output(int **map,int n)
{for (int i = 1; i <= n; i++){for (int j = 1; j <= n; j++)printf("%5d",map[i][j]);printf("\n");}/*****释放空间****************************** */for (int i = 0; i < n+1; i++){free(map[i]);}free(map);/*****     ******************************** */return ;
}

代码采用动态分配内存和模块化,对于宏定义0x3f3f3f,为无穷大,可以通过memset来定义,具体详情参考博客:【C语言中如何表示无穷大】_c语言的无穷大如何表示-CSDN博客

 Dijkstra

        Dijkstra可以用来解决单源最短路径,但不能判断负权回路问题。基于「贪心」、「广度优先搜索」、「动态规划」。

        我们可以确定,与顶点1直接连接的点的所有边中,一定有一个是最短路径,即两点之间没有办法通过插入其它顶点来缩短路径。因为插入的点也是要连接1的,如果说插入过后路径变短,那这个点就不是与1连接的最短路径。(a)图中1顶点与2、3、5都是直接连接。

        我们确定与1连接的最短路径的顶点后,如图(a)是3,那么1到3便是一条最短路径,我们通过这条最短路径来更新1到其它顶点的路径(1通过3),再通过3顶点重复上边操作,这样每次确定的都是最短路径,我们就可以得到1顶点到其它任意顶点的最短路径

        那么我们如何设计这个程序呢?

        因为是单源最短路径,所以我们可以用一个一维数组dis来存储一个顶点到其它顶点的最短路径。

        例题

        求1号顶点到2、3、4、5、6号顶点的最短路径。邻接矩阵如下:

 

         初始化dis:

         我们发现最短的为1到2,那么这个就是这一轮中的最短路径,可以用book数组标记,

      防止下一轮中被算为新的最短路径 (1到1已经被标记),那么我们就再更新1通过2到各个顶点的最短路径,我们发现3和4的被更新的,这个过程叫做松弛。

        

        接下来我们在没有被book标记的其余路径下继续寻找最短路径,然后继续更新,最短路径为1->4(其实是2->4): 

         这一轮最短路径为8,继续更新,因为从3出发的只有3-5,所以本轮中,值更新了1-5这一条路线,1-6不属于更新范围内:

         这一轮最短路径为5,松弛完毕后的dis数组:

        

        最后剩余一条,即是最短路径:

 

         按着红线走经过的顶点便是最短路径,我们会发现每个点只会延伸出一条红线。

         核心代码:

    for (int j = 1,min,min_u; j <= n-1 ; j++){min=inf;min_u=0;for (int i = 1; i <= n; i++)/*选出未更新中最小的*/{if (book[i]==0&&dis[i]<min){min=dis[i];min_u=i;}}book[min_u]=1;for (int i = 1; i <= n; i++)/*松弛*/{if (dis[i]>dis[min_u]+map[min_u][i]){dis[i]=dis[min_u]+map[min_u][i];}} }

         

        再加上输入输出和初始化模块,得到完整代码。 

        输入:输入m+1行,第一行输入n和m代表顶点和边,接下来的m行每行包含三个变量x,y,z表示从x到y的距离为z。

6 9

1 2 1

1 3 12

2 3 9

2 4 3

3 5 5

4 3 4

4 5 13

4 6 15

5 6 4

        输出:dis数组

    0    1    8    4   13   17

         完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define inf 0x3f3f3f3f/*无穷大*/int** memory_two(int n);/*二维数组分配*/
int* memory_one(int n);/*一维数组分配*/
void  init(int **map,int *dis,int n,int m);/*dis和map初始化*/
void free_output(int **map,int *dis,int n);/*释放内存和输出结果*/int main()
{int n,m;scanf("%d%d",&n,&m);int* dis=memory_one(n+1);/*不用0,故传n+1*/int* book=memory_one(n+1);/*不用0,故传n+1*/int** map=memory_two(n+1); /*不用0,故传n+1*/init(map,dis,n,m);book[1]=1;for (int j = 1,min,min_u; j <= n-1 ; j++){min=inf;min_u=0;for (int i = 1; i <= n; i++)/*选出未更新中最小的*/{if (book[i]==0&&dis[i]<min){min=dis[i];min_u=i;}}book[min_u]=1;for (int i = 1; i <= n; i++)/*松弛*/{if (dis[i]>dis[min_u]+map[min_u][i]){dis[i]=dis[min_u]+map[min_u][i];}} }free_output(map,dis,n);return 0;
}int** memory_two(int n)
{/*****二维数组动态分配内存和初始化************************ */int **map;map=(int **)malloc(sizeof(int*)*n);//二级指针分配n个一级指针for (int i = 0; i < n; i++)map[i]=(int *)malloc(sizeof(int)*n);//一级指针开辟空间for (int i = 0; i < n; i++)memset(map[i],inf,sizeof(int)*n);//二维数组初始化/******可用map[100][100]={0}代替************************ */return map;
}int* memory_one(int n)
{int *a=(int *)malloc(n*sizeof(int));memset(a,0,sizeof(int)*n);return a;
}void  init(int **map,int *dis,int n,int m)
{for (int i = 0,x=0,y=0,flag; i < m; i++){scanf("%d %d %d",&x,&y,&flag);map[x][y]=flag;}for (int i = 0; i <= n; i++)/*顶点i到i为0*/map[i][i]=0;for (int i = 0; i <= n; i++)/*dis初始化*/dis[i]=map[1][i];return ;
}void free_output(int **map,int *dis,int n)
{for (int j = 1; j <= n; j++)printf("%5d",dis[j]);printf("\n");/*****释放空间****************************** */for (int i = 0; i < n+1; i++){free(map[i]);}free(map);free(dis);/*****     ******************************** */return ;
}

       这个算法的时间复杂度为O(N^2),其中每次找到离1号顶点最近的顶点的时间复杂度为O(N),这里我们可以用“堆”来优化,使得这一部分时间复杂度降到log(N),另外对于边数M少于N^2的稀疏图来说(我们把M远小于N^2的图叫做稀疏图,而M较大的图称为稠密图),我们可以用邻接表来代替邻接矩阵,使得时间复杂度优化到O(N+M)logN。最坏的情况下M就是N^2,这样的话(M+N)logN要比N^2还要大。但是大多数情况下并不会有那么多边,因此(M+N)logN要比N^2小得多。

邻接表 (Adjacency List

                邻接表是是一种顺序分配和链式分配相结合的存储结构,这里只介绍在有向图中的使用。

        这里我们用数组来实现链表,我们首先创建五个数组:first,next,u,v,w;first可以理解为是头节点,next存储下一条边的编号。u,v,w表示从u到v的边的权重为w。

        我们从两个方面来理解邻接表,一方面是邻接表的创建,一方面是邻接表的读取

        我们存储一组数据来模拟:

4 5

1 4 9

2 4 6

1 2 5

4 3 8

1 3 7

        邻接表的创建核心代码:

    for (int i = 1; i <= m; i++){scanf("%d%d%d",&u[i],&v[i],&w[i]);next[i]=first[u[i]];first[u[i]]=i;   }

         将每条边的信息存储到u、v、w中,first的初始值为-1(表示该顶点没有边),

        读入第一条边,u[i]为1,故我们先将first[1]里面的值存入next[i],再将next[i]的下标i存入first[u[i]],其中next[i]的下标i对应u[i]、v[i]、w[i];

        读入第二条边,同样的操作。

        读入第三条边,注意此时的u[i]为1,虽然还是进行了同样的操作,first的值更新为了3,对应的next[3](这里的3代表第三次读入的,而不是顶点3)存储的是前一个first[1]=1(这里的1也代表第一次读入,同时也和next[1]有关系。),

        读入第四次和第五次,也是同样的操作。

         

         这样我们便读入了所有的边,邻接表的创建已经完成了,如果还有点不理解,先看邻接表的读入,接着往下看。

        

        图片显示了与1直接先连的边, 5、3、1表示第5次读入的边的信息、第3次读入的边的信息,第1次读入边的信息。对应边:1->3、1->2、1->4,所以邻接表里面并没有直接存储对应的边,而是存储的第几次读入。

  要找与1顶点直接连接的边,就找first[1];通过k=first[i]中的k,k有两个含义,一个含义表示一条与1顶点直接连接的边是第k次读入的,其中u[k]为1,v[k]为对应的边,权重为w[k];另一个含义:通过k=next[k],这又得到一条与1顶点直接连接的边是第k次读入的,其中u[k]为1,v[k]为对应的边,权重为w[k];直到k为-1,表示所有与1相连的边都已经找到。

    邻接表的读取核心代码:

        int k=first[1];/*顶点*/while (k!=-1){printf("%d %d %d",u[k],v[k],w[k]);k=next[k];}

        我们会发现,遍历的顺序和读入时候的顺序相反。因为在为每个顶点插入边的时候都是直接插入"链表"的首部而不是尾部。 

        使用邻接表来存储图的时间空间复杂度为O(M),遍历每一条边的时间复杂度也是O(M),如果一个图是稀疏图的话,M要远小于N^2。

        邻接表实现Dijkstra

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define inf 0x3f3f3f3f/*无穷大*/int* memory_one(int n,int x);/*一维数组分配*/
void  init(int *u,int *v,int *w,int *first,int *next,int *dis,int m);/*dis和map初始化*/int main()
{int n,m;scanf("%d%d",&n,&m);int *u,*v,*w;/*从u到v,权重为w*/int *first,*next;/*邻接表*/int* dis=memory_one(n+1,0x3f3f3f3f);/*不用0,故传n+1*/int* book=memory_one(n+1,0);/*不用0,故传n+1*/u=memory_one(m+1,0);/*不用0,比m个数多一*/v=memory_one(m+1,0);/*不用0,比m个数多一*/w=memory_one(m+1,0);/*不用0,比m个数多一*/first=memory_one(n+1,-1);next=memory_one(n+1,0);// int** map=memory_two(n+1); /*不用0,故传n+1*/init(u,v,w,first,next,dis,m);book[1]=1;for (int j = 1,min,min_u; j <= n-1 ; j++){min=inf;min_u=0;for (int i = 1; i <= n; i++)/*选出未更新中最小的*/{if (book[i]==0&&dis[i]<min){min=dis[i];min_u=i;}}book[min_u]=1;int k=first[min_u];/*顶点*/while (k!=-1){if (dis[v[k]]>dis[u[k]]+w[k]){dis[v[k]]=dis[u[k]]+w[k];}k=next[k];}}for (int j = 1; j <= n; j++)printf("%5d",dis[j]);printf("\n");free(u);free(v);free(w);free(first);free(next);free(dis);free(book);return 0;
}int** memory_two(int n)
{/*****二维数组动态分配内存和初始化************************ */int **map;map=(int **)malloc(sizeof(int*)*n);//二级指针分配n个一级指针for (int i = 0; i < n; i++)map[i]=(int *)malloc(sizeof(int)*n);//一级指针开辟空间for (int i = 0; i < n; i++)memset(map[i],inf,sizeof(int)*n);//二维数组初始化/******可用map[100][100]={0}代替************************ */return map;
}int* memory_one(int n,int x)
{int *a=(int *)malloc(n*sizeof(int));memset(a,x,sizeof(int)*n);return a;
}void  init(int *u,int *v,int *w,int *first,int *next,int *dis,int m)
{   for (int i = 1; i <= m; i++){scanf("%d%d%d",&u[i],&v[i],&w[i]);next[i]=first[u[i]];first[u[i]]=i;   }for (int k=first[1]; k!=-1;)/*dis初始化*/{dis[v[k]]=w[k];k=next[k];}dis[1]=0;    return ;
}

        负权边问题 

                Dijkstra是先找到直接相连的对短边(假设为A)来松弛的,但如果有负边的情况下,松弛过后不能保证A还是最短边,故不能解决负权边问题。

Bellman-Ford

        Bellman-ford 算法也是解决单源最短路径,但Dijkstra 算法比
更具普遍性,Dijkstra是先找最短边,而Bellman-Ford是通过对每条边直接遍历。它对边没有要求,可以处理负权边与负权回路。缺点是时间复杂度过高,高达O(NM)。

         核心代码:

    for (int i = 1; i <= n-1; i++)  for (int j = 1; j <= m; j++)if (dis[v[j]]>dis[u[j]]+w[j])dis[v[j]]=dis[u[j]]+w[j];

         因为也是解决单源最短路径,故还是建立dis,外循环n-1层,内循环m层,我们直接上案例:

        例题

         先初始化dis数组:

         

         第一轮对所有边进行松弛操作,更新了2和5:

         第二轮对所有边进行松弛操作,更新了3和4:

         

        这里注意,如果我们调换 “3 4 3”和”2 3 2“的读入顺序,先读入“3 4 3”,这里就会只更新3而不更新4了。“dis[v[j]]>dis[u[j]]+w[j]”中,如过dis[u[j]]为inf,那么就肯定不会更新,通过这个规则我们就得到了一个规律

          第1轮在对所有的边进行松弛后,得到的是源点最多经过一条边到达其他顶点的最短距离;第2轮在对所有的边进行松弛后,得到的是源点最多经过两条边到达其他顶点的最短距离;第3轮在对所有的边进行松弛后,得到的是源点最多经过三条边到达其他顶点的最短距离......。当然这句话有一个bug(是算法程序的bug,不是算法的bug,如果用队列优化就不会出现,完全遵循该规律),就是和读入顺序相关,在第二轮松弛中,如果先读入“2 3 2”,那么就会更新了dis[3],那么在读入“3 4 3”的时候就会更新经过三个边的顶点,而读入顺序调返边不会发生。

        还有一个问题,对所有边遍历几轮结束呢?这里我们选的是n-1轮,是因为在含有n个顶点的图中,任意两点之间的最短路径最多包含n-1条边,最坏的情况下,n个顶点连成一条直线,那我们只需要n-1次循环就可以完成。

        我们直接给出第三轮和第4轮松弛,这里我们发现第4轮松弛可以省略,因为距离1顶点最远的顶点4也只有3条边。对于这种情况,我们可以加一个check来判断dis有没有变化,从而提早结束循环。

        

         

       负权回路问题

                负权回路是指图中存在至少一个环,使得经过该环的路径权重之和为负值。负权回路的存在可能导致在求解最短路径的过程中出现问题。例如,对于一般的最短路径算法(如 Dijkstra 或 Bellman-Ford 算法),它们无法处理图中存在负权回路的情况,因为在存在负权回路的图中,最短路径可能是无限小的。

                Bellman-Ford可以判断负权回路,如果在进行n-1轮松弛后,仍然存在:

            if (dis[v[j]]>dis[u[j]]+w[j])dis[v[j]]=dis[u[j]]+w[j];

        也就是进行n-1轮松弛后,仍然可以松弛成功那就代表存在负权回路。判断代码如下:

    int flag=0;for (int i = 1; i <= m; i++){if (dis[v[i]]>dis[u[i]]+w[i])flag=1;}if (flag==1){printf("This diagram has a negative weight loop!");/*负权回路是指图中存在至少一个环,使得经过该环的路径权重之和为负值*/}

        再加上输入输出和初始化模块,得到完整代码。 

        输入:输入m+1行,第一行输入n和m代表顶点和边,接下来的m行每行包含三个变量x,y,z表示从x到y的距离为z。

5 5

2 3 2

1 2 -3

1 5 5

4 5 2

3 4 3

        输出:dis数组

    0   -3   -1    2    4

         完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define inf 0x3f3f3f3f/*无穷大*/int* memory_one(int n,int x);/*一维数组分配*/
void  init(int *u,int *v,int *w,int m);/*dis和map初始化*/int main()
{int n,m;scanf("%d%d",&n,&m);int *u,*v,*w;/*从u到v,权重为w*/int* dis=memory_one(n+1,0x3f3f3f3f);/*不用0,故传n+1*/int* bak=memory_one(n+1,0x3f3f3f3f);/*不用0,故传n+1*/dis[1]=0;u=memory_one(m+1,0);/*不用0,比m个数多一*/v=memory_one(m+1,0);/*不用0,比m个数多一*/w=memory_one(m+1,0);/*不用0,比m个数多一*/// int** map=memory_two(n+1); /*不用0,故传n+1*/init(u,v,w,m);for (int i = 1,check; i <= n+2; i++){for (int i = 1; i <= n ; i++)bak[i]=dis[i];for (int j = 1; j <= m; j++){if (dis[v[j]]>dis[u[j]]+w[j]){dis[v[j]]=dis[u[j]]+w[j];}} /*检查是否更新*/check=0;for (int i = 1; i <= n ; i++)if (bak[i]!=dis[i]){check=1;break;}if (check==0)break;     /*********** */}/*****判断负权回路*****/int flag=0;for (int i = 1; i <= m; i++){if (dis[v[i]]>dis[u[i]]+w[i])flag=1;}if (flag==1){printf("This diagram has a negative weight loop!");/*负权回路是指图中存在至少一个环,使得经过该环的路径权重之和为负值*/}/***************** */for (int j = 1; j <= n; j++)printf("%5d",dis[j]);printf("\n");free(u),free(v),free(w);free(dis);return 0;
}int** memory_two(int n)
{/*****二维数组动态分配内存和初始化************************ */int **map;map=(int **)malloc(sizeof(int*)*n);//二级指针分配n个一级指针for (int i = 0; i < n; i++)map[i]=(int *)malloc(sizeof(int)*n);//一级指针开辟空间for (int i = 0; i < n; i++)memset(map[i],inf,sizeof(int)*n);//二维数组初始化/******可用map[100][100]={0}代替************************ */return map;
}int* memory_one(int n,int x)
{int *a=(int *)malloc(n*sizeof(int));memset(a,x,sizeof(int)*n);return a;
}void  init(int *u,int *v,int *w,int m)
{   for (int i = 1; i <= m; i++){scanf("%d%d%d",&u[i],&v[i],&w[i]);   }    return ;
}

       在每一次松弛操作后,就会有一些顶点已经求得其最短路,但是每次还要判断是否需要松弛,我们可以用队列优化这一问题,而且可以解决上述所说的“BUG”。

Bellman-Ford  队列优化

               采用队列,可以实现每次仅对上一轮最短路更新的点进行松弛,不需要遍历所有边。

队列初始化:

    head=1;tail=1;que[tail]=1;tail++;

核心代码:

    book[1]=1;while (head<tail){int k=first[que[head]];/*顶点*/while (k!=-1){if (dis[v[k]]>dis[u[k]]+w[k]){dis[v[k]]=dis[u[k]]+w[k];if (book[v[k]]==0){que[tail]=v[k];/*入队*/tail++;book[v[k]]=1;}}k=next[k];}book[que[head]]=0;head++;/*出队*/}

        其它部分一样,不再叙述例题。

完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define inf 0x3f3f3f3f/*无穷大*/int* memory_one(int n,int x);/*一维数组分配*/
void  init(int *u,int *v,int *w,int *first,int *next,int *dis,int m);/*dis和map初始化*/int main()
{int n,m;scanf("%d%d",&n,&m);int *u,*v,*w;/*从u到v,权重为w*/u=memory_one(m+1,0);/*不用0,比m个数多一*/v=memory_one(m+1,0);/*不用0,比m个数多一*/w=memory_one(m+1,0);/*不用0,比m个数多一*/int* first=memory_one(n+1,-1);/*邻接表*/int* next=memory_one(n+1,0);/*邻接表*/int* dis=memory_one(n+1,0x3f3f3f3f);/*不用0,故传n+1*/int* book=memory_one(n+1,0);/*不用0,故传n+1*/int  *que=memory_one(n+n,0);;/*队列*/int head,tail;init(u,v,w,first,next,dis,m);/*队列初始化*/head=1;tail=1;que[tail]=1;tail++;/********** */book[1]=1;while (head<tail){int k=first[que[head]];/*顶点*/while (k!=-1){if (dis[v[k]]>dis[u[k]]+w[k]){dis[v[k]]=dis[u[k]]+w[k];if (book[v[k]]==0){que[tail]=v[k];/*入队*/tail++;book[v[k]]=1;}}k=next[k];}book[que[head]]=0;head++;/*出队*/}for (int j = 1; j <= n; j++)printf("%5d",dis[j]);printf("\n");free(u);free(v);free(w);free(first);free(next);free(dis);free(book);return 0;
}int* memory_one(int n,int x)
{int *a=(int *)malloc(n*sizeof(int));memset(a,x,sizeof(int)*n);return a;
}void  init(int *u,int *v,int *w,int *first,int *next,int *dis,int m)
{   for (int i = 1; i <= m; i++){scanf("%d%d%d",&u[i],&v[i],&w[i]);next[i]=first[u[i]];first[u[i]]=i;   }dis[1]=0;    return ;
}

《啊哈,算法!》勘误注意: 

由于用的是邻接表,真正读入的顺序是倒序的,而书中给的例子入队顺序是正序的:

 正确的应该是:1、5、2、3、5、4

最短路径算法对比分析

        图片来源《啊哈!算法》    

         Flody算法总体复杂度高,但它求的是多源最短路径,其它三个均为单源最短路径,但你也可以再嵌套一层循环来实现(我没有尝试)。如果要求所有点对间的最短路径或者数据范围比较小,可以用Flody算法,Dijkstra算法无法解决负权边,但具有拓展性(怎么拓展呢?),堆优化的Dijkstra的时间复杂度达到O(MlogN)(学完堆回来补),当有负权边时,使用Bellman-Ford来实现。因此我们选择最短路径算法时,要根据实际需求和每一种算法的特性,选择合适的算法。

参考文献

《啊哈!算法》

图详解第六篇:多源最短路径--Floyd-Warshall算法(完结篇)-腾讯云开发者社区-腾讯云

Dijkstra算法详解 通俗易懂 - 知乎

Bellman-ford 算法 - 知乎

什么是负权回路 - AcWing

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/62644.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一些优秀的布隆过滤器介绍

&#x1f680; 博主介绍&#xff1a;大家好&#xff0c;我是无休居士&#xff01;一枚任职于一线Top3互联网大厂的Java开发工程师&#xff01; &#x1f680; &#x1f31f; 在这里&#xff0c;你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人&#xff0c;我不仅热衷…

YOLOv8模型pytorch格式转为onnx格式

一、YOLOv8的Pytorch网络结构 model DetectionModel((model): Sequential((0): Conv((conv): Conv2d(3, 64, kernel_size(3, 3), stride(2, 2), padding(1, 1))(act): SiLU(inplaceTrue))(1): Conv((conv): Conv2d(64, 128, kernel_size(3, 3), stride(2, 2), padding(1, 1))(a…

论文解读:Reward criteria impact on the performance ofreinforcement learning...

Reward criteria impact on the performance ofreinforcement learning agent for autonomous navigation 译文&#xff1a; 奖励准则对自主导航强化学习agent性能的影响 摘要&#xff1a; 在强化学习中&#xff0c;主体在环境中的每个时间步采取行动&#xff08;遵循策略&…

glog在vs2022 hello world中使用

准备工作 设置dns为阿里云dns 223.5.5.5&#xff0c;下载cmake&#xff0c;vs2022&#xff0c;git git clone https://github.com/google/glog.git cd glog mkdir build cd build cmake .. 拷贝文件 新建hello world并设置 设置预处理器增加GLOG_USE_GLOG_EXPORT;GLOG_NO_AB…

搜索二维矩阵 II(java)

题目描述 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 代码思路&#xff1a; 用暴力算法&#xff1a; class Solution {public boolean searchMatrix(…

基于vite创建的react18项目的单元测试

题外话 最近一个小伙伴进了字节外包&#xff0c;第一个活就是让他写一个单元测试。 嗯&#xff0c;说实话&#xff0c;在今天之前我只知道一些理论&#xff0c;但是并没有实操过&#xff0c;于是我就试验了一下。 通过查询资料&#xff0c;大拿们基本都说基于vite的项目&…

GY302光照传感器模块详解

目录 一、引言 二、功能特点 三、工作原理 四、引脚功能 五、应用场景 六、使用方法 七、总结 一、引言 在当今科技飞速发展的时代&#xff0c;传感器技术在各个领域都发挥着至关重要的作用。光照传感器作为一种能够感知环境光照强度的设备&#xff0c;广泛应用于农业、…

pip install causal-conv1d==1.1.1报错

Building wheels for collected packages: causal-conv1d Building wheel for causal-conv1d (setup.py) ... error error: subprocess-exited-with-error python setup.py bdist_wheel did not run successfully. │ exit code: 1 ╰─> [8 lines of output]…

【WRF后处理】WRF模拟效果评价及可视化:MB、RMSE、IOA、R

【WRF后处理】模拟效果评价及可视化 准备工作模型评价指标Python实现代码Python处理代码:导入站点及WRF模拟结果可视化图形及评价指标参考在气象和环境建模中(如使用 WRF 模型进行模拟),模型性能评价指标是用于定量评估模拟值与观测值之间偏差和拟合程度的重要工具。 本博客…

facebook欧洲户开户条件有哪些又有何优势?

在当今数字营销时代&#xff0c;Facebook广告已成为企业推广产品和服务的重要渠道。而为了更好地利用这一平台&#xff0c;广告主们需要理解不同类型的Facebook广告账户。Facebook广告账户根据其属性可分为多种类型&#xff0c;包括个人广告账户、企业管理&#xff08;BM&#…

Scala学习记录,全文单词统计

package test32 import java.io.PrintWriter import scala.io.Source //知识点 // 字符串.split("分隔符"&#xff1a;把字符串用指定的分隔符&#xff0c;拆分成多个部分&#xff0c;保存在数组中) object test {def main(args: Array[String]): Unit {//从文件1.t…

FreeRTOS综合代码实例---多级菜单的设计

本文介绍一套基于FreeRTOS的综合代码实例&#xff0c;目标是通过模块化的编程方式实现对多种硬件功能的管理。该实例沿用《FreeRTOS综合代码实例-OLED版本》的框架&#xff0c;重点改进为TFT LCD显示&#xff0c;同时保留了多级菜单的实现。项目链接&#xff1a;FreeRTOS综合代…

OpenMP出现Stack Overflow及其疑问

今天对着《OpenMP核心技术指南》练习OpenMP&#xff0c;其中一个案例: #include <stdio.h> #include <math.h> #include <omp.h>#define ITER 100000000void main() {int i;double A[ITER];for (i 0; i < ITER; i)A[i] 2.0 * i;#pragma omp parallel{/…

PYNQ 框架 - 时钟系统 + pl_clk 时钟输出不准确问题

目录 1. 简介 2. PS 时钟计算 2.1 计算框架 2.2 KV260 的参考时钟 2.3 PL_CLK 设置 3. 测试 3.1 Block design 3.2 引脚绑定 3.3 使用 AD2 测量 3.4 调整分频 4. PYNQ 时钟驱动 4.1 源码解析 4.2 查看 PL_CLK 4.3 配置 PL_CLK 5. 总结 1. 简介 ZYNQ MPSoC 具有…

SQL进阶——C++与SQL进阶实践

在C开发中&#xff0c;SQL数据库的操作是开发者常见的任务之一。虽然前面我们已经介绍了如何在C中通过数据库连接执行基本的SQL查询&#xff0c;但在实际项目中&#xff0c;我们通常需要更加复杂和高效的数据库操作。存储过程与函数的调用、复杂SQL查询的编写、以及动态构造SQL…

【Zookeeper】四,Zookeeper节点类型、通知、仲裁、会话

文章目录 Zookeeper的架构znode的版本Zookeeper的节点类型层级树状结构znode的不同类型 Zookeeper监视与通知通知的类型 Zookeeper的仲裁Zk的会话会话的生命周期 Zookeeper的架构 Zookeeper的服务器端运行两种模式&#xff1a;独立模式&#xff08;standalone&#xff09;和仲…

Mac安装及合规无限使用Beyond Compare

文章目录 Beyond CompareBeyond Compare简介Beyond Compare安装Beyond Compare到期后继续免费使用 Beyond Compare Beyond Compare简介 Beyond Compare 是一款由 Scooter Software 开发的文件和文件夹比较工具。它主要用于对比两个文件或文件夹之间的差异&#xff0c;并支持文…

[极客大挑战 2019]PHP--详细解析

信息搜集 想查看页面源代码&#xff0c;但是右键没有这个选项。 我们可以ctrlu或者在url前面加view-source:查看&#xff1a; 没什么有用信息。根据页面的hint&#xff0c;我们考虑扫一下目录看看能不能扫出一些文件. 扫到了备份文件www.zip&#xff0c;解压一下查看网站源代码…

毫米波雷达技术:(五)距离-多普勒图谱,以及 FMCW 信号帧结构的设计

(一) 距离-多普勒图谱&#xff08; R a n g e − D o p p l e r F F T Range-Doppler~FFT Range−Doppler FFT &#xff08; 2 D − F F T 2D-FFT 2D−FFT&#xff09;的结果&#xff09;: 1&#xff09;range-bins&#xff08;距离单元&#xff09;&#xff1a; 上述步骤②的…

Mybatis:CRUD数据操作之多条件查询及动态SQL

Mybatis基础环境准备请看&#xff1a;Mybatis基础环境准备 本篇讲解Mybati数据CRUD数据操作之多条件查询 1&#xff0c;编写接口方法 在 com.itheima.mapper 包写创建名为 BrandMapper 的接口。在 BrandMapper 接口中定义多条件查询的方法。 而该功能有三个参数&#xff0c;…