MENU

最短路问题 | ACM

March 18, 2019 • 我爱学习

最短路问题

Floyd (有向图)

  • 首先是需要构造邻接矩阵,利用二维数组构造,每一行代表一个起始点,每一列代表一个终点,数组中的值代表从起点到终点的距离(即权重),起终点相同即权重为 0,两点之间没有通路则权重为∞。
  • 现在邻接矩阵中存的就是,从起点直接到终点的距离,但有时候可能两个点之间的距离并不是最短,所以我们还需要一个或者多个中间点 k。例如从点 3 走到点 2 的距离是无穷大,但从点 3 经点 1 再到点 2 的距离为 9。
  • 算法过程大致如下,
  • 先假设我们的中间点为 k,当 k=1 时,代表途中经过的中间点为 1,然后开始遍历邻接矩阵。假设此时遍历到第三行第二列,则为以 3 为起点 2 为终点且经过点 1 的距离,式子为 map[3][1]+map[3][2] 将这一计算结果与原本 map[3][2] 的值进行比较,取较小值更新。

经过一个或不经过中间点时,两点之间的最小距离

  • 当邻接链表以 k=1 被遍历完一遍之后,邻接链表的内容就已经变为经过一个或不经过中间点时,两点之间的最小距离,此时进行 k=2 的遍历。完成之后邻接链表的内容变为经过一个、两个或不经过中间点时,两点之间的最小距离。以此类推。。。

    ###Floyd 方法核心代码

    • void floyd()
    • {
    • for(int k = 0; k < n; k++)
    • for(int i = 0; i < n; i++)
    • for(int j = 0; j < n; j++)
    • map[i][j] = min(map[i][j], map[i][k] + map[k][j]);
    • }

例题

  • #include<algorithm>
  • #include<iostream>
  • #include<cstring>
  • #define inf 0x3f3f3f3f //代表无穷大using namespace std;
  • int mapp[205][205];
  • int n,m;
  • void floyd(){
  • for(int k=0;k<n;k++){
  • for(int i=0;i<n;i++){
  • for(int j=0;j<n;j++){
  • mapp[i][j]=min(mapp[i][j],mapp[i][k]+mapp[k][j]);
  • }
  • }
  • }
  • }
  • void init(){
  • for(int i=0;i<204;i++){
  • for(int j=0;j<204;j++){
  • if(i==j){
  • mapp[i][j]=0;
  • }
  • else{
  • mapp[i][j]=inf;
  • }
  • }
  • }
  • }
  • int main(){
  • while(~scanf("%d %d",&n,&m)){
  • init();
  • int a,b,x,s,t;
  • for(int i=0;i<m;i++){
  • scanf("%d %d %d",&a,&b,&x);
  • if(x<mapp[a][b]){
  • mapp[a][b]=x;
  • mapp[b][a]=x;
  • }
  • }
  • floyd();
  • scanf("%d %d",&s,&t);
  • if(mapp[s][t]==inf){
  • printf("-1\n");
  • continue;
  • }
  • printf("%d\n",mapp[s][t]);
  • }
  • }

Djkstar(有单另的答案矩阵)

  • 首先也是先构建邻接矩阵,不过这个邻接矩阵为双向。即 mapi == mapj;
  • 与 Floyd 不同的是,Djkstar 还需要一个单另的一维答案矩阵,更新时在答案矩阵上进行更新。
  • 算法过程大致如下,遍历找到距离当前点最近的点 p,将 从起点走到当前点的最短距离再加上从当前点到 p 点的距离 与答案矩阵中从起点直接到的距离相比较,更新成较小的那个。这样遍历所有的点,最后答案矩阵中相应点下的值就是从起点到相应点的最短路径。注:Djkstar 方法还需要一个一位数组来记录哪些点已经被遍历过。

具体图解过程如下:

上图是最开始的图、答案矩阵、和邻接矩阵

  • 此时假设 1 为起点,则把邻接矩阵第一横行拷贝进答案矩阵

此时 1 为当前点

  • 很显然距离点 1 最近的点为点 2,此时走过的距离为 7,这时候从点 2 走到点 4 的距离是 15+722;小于正无穷,所以更新为 22;且点 2 走到点 3 的距离是 10+717;大于 9,所以不更新。

此时 2 为当前点

  • 此时距离 2 最近的点是点 3,所以来到点 3;此时答案矩阵中点 3 对应的距离是 9;所以从点 3 到点 4 的距离为 9+1120,小于 22,所以点 4 更新为 20. 从点 3 到点 6 的距离为 9+211,小于 14,所以点 6 更新为 14.

此时 3 为当前点

  • 如此这般下去,直到遍历完所有的点。

###Djkstar 方法核心代码

  • //pos为当前点,minn为离当前点最近的点
  • //mark[]是记录所有点是否被遍历过的数组
  • //ans[]是答案矩阵
  • //mapp[]是邻接矩阵
  • for(int i=1;i<n;i++){
  • int pos,minn=inf;
  • for(int j=0;j<n;j++){
  • if(mark[j]==0 && ans[j]<minn){//找没有走过的点,并且找距离起始点最短的路
  • pos=j;
  • minn=ans[j];
  • }
  • }
  • mark[pos]=1;
  • for(int j=0;j<n;j++){
  • if(ans[j]>ans[pos]+mapp[pos][j]){
  • ans[j]=ans[pos]+mapp[pos][j];
  • }
  • }
  • }

例题

  • #include<stdio.h>
  • #define inf 0x3f3f3f3f
  • int map[205][205];
  • int ans[205];
  • int mark[205];
  • int main()
  • {
  • int n,m;
  • int a,b,x;
  • int s,e;
  • while(~scanf("%d %d",&n,&m))
  • {
  • for(int i = 0;i < 205;i++)
  • {
  • for(int j = 0;j < 205;j++)
  • {
  • map[i][j] = inf;
  • }
  • }
  • for(int i = 0;i < 205;i++)
  • {
  • map[i][i] = 0;
  • mark[i] = 1;
  • }
  • //地图初始化
  • for(int i = 0;i < m;i++)
  • {
  • scanf("%d %d %d",&a,&b,&x);
  • if(x < map[a][b]){
  • map[a][b] = x;
  • map[b][a] = x;
  • }
  • }
  • scanf("%d %d",&s,&e);
  • //接收数据
  • for(int i = 0;i < n;i++)
  • {
  • ans[i] = map[s][i];
  • }
  • //答案矩阵初始化
  • mark[s] = 0;
  • for(int i = 0;i < n;i++)
  • {
  • int pos,minn = inf;
  • for(int j = 0;j < n;j++)
  • {
  • if(mark[j] == 1 && ans[j] < minn)
  • {
  • pos = j;
  • minn = ans[j];
  • }
  • }
  • mark[pos] = 0;
  • for(int j = 0;j < n;j++)
  • {
  • if(ans[j] > ans[pos] + map[pos][j])
  • ans[j] = ans[pos] + map[pos][j];
  • }
  • }
  • if(ans[e] == inf)
  • printf("-1\n");
  • else
  • printf("%d\n",ans[e]);
  • }
  • return 0;
  • }

SPFA

Last Modified: September 8, 2021