第6章 最短路径

简介: 第6章 最短路径

第6章 最短路径

第1节 只有五行的算法–Floyd-Warshall

任意两点之间的最短路径。

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

第2节 Dijkstra算法–通过边实现松弛

从指定点到其余点的最短路径。“单源最短路径”。

基本思想是每次找到源点最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到所有点的最短路径。

package ch6;

import java.util.Scanner;

public class Test2 {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();//n个顶点
    int m = sc.nextInt();//m条边
    final int inf = 999999;
    int [][] e = new int[10][10];//存储地图
    //初始化e
    for(int i=1;i<=n;i++) {
      for(int j=1;j<=n;j++) {
        if(i==j) {
          e[i][j] = 0;
        }else {
          e[i][j] = inf;
        }
      }
    }
    //读入边
    for(int i=1;i<=m;i++) {
      int t1 = sc.nextInt();
      int t2 = sc.nextInt();
      int t3 = sc.nextInt();
      e[t1][t2] = t3;
    }
    
    //初始化dis数组, 1到各个顶点的路程
    int[] dis = new int[10];
    for(int i=1;i<=n;i++) {
      dis[i] = e[1][i];
    }
    //初始化book
    int[] book = new int[10];
    book[1] = 1;
    
    //Dijkstra算法核心
    for(int i=1;i<=n-1;i++) {
      int min = inf;
      int u = 1;
      //遍历,找到当前最小距离顶点u 
      for(int j=1;j<=n;j++) {
        if(book[j]==0 &&dis[j]<min) {
          min = dis[j];
          u=j;
        }
      }
      book[u] = 1;
      //通过u对其余顶点进行扩展  “松弛”
      for(int v=1;v<=n;v++) {
        if(e[u][v]<inf) {
          if(dis[v]>dis[u]+e[u][v]) {
            dis[v] = dis[u]+e[u][v];
          }
        }
      }
    }
    //输出结果
    for(int i=1;i<=n;i++) {
      System.out.print(dis[i]+" ");
    }
  }
}

对于边数m小于n^2的稀疏图,可以用邻接表代替邻接矩阵,降低时间复杂度。

Scanner sc = new Scanner(System.in);
int n = 5;
int m = 4;

int[] u = new int[m+1];//顶点(起点)
int[] v = new int[m+1];//顶点(目的)
int[] w = new int[m+1];//路程

//first和next在遍历顶点i的边时是第一条和下一条,但是存储时是最后一条和上一条。
//first[i]是顶点i的第一条边,有歧义。存储时是最近一次的读入顶点i位起点的边
int [] first = new int[n+1];
//next[i]是边i的下一条边。有歧义。存储时是上一次读入的顶点i为起点的边
int [] next = new int[m+1];


//初始化first ,-1代表顶点没有边
for(int i=1;i<=n;i++){
    first[i] = -1;
}
//读入m条边
for(int i=1;i<=m;i++){
    u[i] = sc.nextInt();
    v[i] = sc.nextInt();
    w[i] = sc.nextInt();
    next[i] = first[u[i]];
    first[u[i]] = i;
}
//遍历每个顶点的边
for(int i=1;i<=n;i++){
    int k = first[i];
    while(k!=-1){
        System.out.print(""+u[k]+" "+v[k]+" "+w[k]+" ");
        k = next[k];
    }
}

第3节 Bellman-Ford —解决负权边

对所有的边进行n-1轮“松弛”操作。经过1轮松弛,得到的是从1号顶点“只能经过一条边”到其余各点的最短路径长度,经过2轮松弛,达到的是从1号顶点“最多经过2条边”到其余各点的最短路径长度。(不包含负权回路,负权回路时不存在最短路径)n个顶点的图中,任意两点之间最短路径最多包含n-1条边。因此,最多经过n-1轮松弛就能得到最短路径。

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

第4节 Bellman-Ford的队列优化

每次仅对最短路径发生变化的点的邻边进行松弛,用队列记录这些发生变化的点。

选取队首顶点u,对u的出边进行松弛,松弛过程中用到的顶点v,把v加入队尾,同一个顶点不用重复加入。对u松弛完成后,将u出队列。接着对其余队首顶点进行重复上述操作。直到队列空为止。

package ch6;

import java.util.Scanner;

public class Test3 {
  public static void main(String[] args) {
    final int inf = 999999;
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();//顶点数
    int m = sc.nextInt();//边数
    
    int[] u = new int[m+1];//顶点(起点)
    int[] v = new int[m+1];//顶点(目的)
    int[] w = new int[m+1];//路程

    //first和next在遍历顶点i的边时是第一条和下一条,但是存储时是最后一条和上一条。
    //first[i]是顶点i的第一条边,有歧义。存储时是最近一次的读入顶点i位起点的边
    int [] first = new int[n+1];
    //next[i]是边i的下一条边。有歧义。存储时是上一次读入的顶点i位起点的边
    int [] next = new int[m+1];
    
    //dis[i]是1号顶点到i号顶点的距离
    int[] dis = new int[n+1];
    for(int i=1;i<=n;i++) {
      dis[i] = inf;
    }
    dis[1] = 0;
    //book[i]为1表示i号顶点在队列中
    int[] book = new int[n+1];

    int[] que = new int[n+2];
    int head = 1;
    int tail = 1;
    
    
    
    
    //初始化first ,-1代表顶点没有边
    for(int i=1;i<=n;i++){
        first[i] = -1;
    }
    //读入m条边
    for(int i=1;i<=m;i++){
        u[i] = sc.nextInt();
        v[i] = sc.nextInt();
        w[i] = sc.nextInt();
        next[i] = first[u[i]];
        first[u[i]] = i;
    }
    
    //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 i=1;i<=n;i++){
      System.out.print(" "+dis[i]);
    }
  }
}

测试数据:
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6

结果:
 0 2 5 9 9

第5节 最短路径算法对比分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-25AwzS6W-1589449758797)(C:\Users\LF\AppData\Roaming\Typora\typora-user-images\1589257208920.png)]

  //输出到各点的最短路径
  for(int i=1;i<=n;i++){
    System.out.print(" "+dis[i]);
  }
}

}


测试数据:

5 7

1 2 2

1 5 10

2 3 3

2 5 7

3 4 4

4 5 5

5 3 6

结果:

0 2 5 9 9




## 第5节 最短路径算法对比分析

[外链图片转存中...(img-25AwzS6W-1589449758797)]


相关文章
|
3月前
|
算法
关于Dijkstra算法
关于Dijkstra算法
|
机器学习/深度学习 人工智能 算法
弗洛伊德算法(求最短路径)
用弗洛伊德算法求最短路径
1342:【例4-1】最短路径问题
1342:【例4-1】最短路径问题
|
存储 算法
秒懂算法 | BFS与最短路径
搜索包括BFS和DFS,这两种算法是算法竞赛的基础。本篇介绍BFS的扩展应用。
481 0
秒懂算法 | BFS与最短路径
|
算法
最短路径——Floyd算法
最短路径——Floyd算法
117 0
|
定位技术
BFS:迷宫最短路径
BFS:迷宫最短路径
|
存储
问题 E: 最短路径问题
问题 E: 最短路径问题
|
算法
最短路径之Dijkstra算法
最短路径之Dijkstra算法
|
算法 C语言
最短路径——Dijkstra算法与Floyd算法
最短路径——Dijkstra算法与Floyd算法
193 0
最短路径——Dijkstra算法与Floyd算法