前言
最短路径算法一般是广搜算法。有多种情况,单源最短路径,多源最短路径,负权回路等问题
多源最短路径Floyd-Warshall算法
- 优点:实现简单,代码量少
- 缺点:时间复杂度为O(n^3),不能解决负权回路的问题
使用邻接矩阵存储法存储图
遍历每个节点作为中转点,取距离的最小值
比如1到3,a[1][3]=6
以2作为中转点,即a[1][2]+a[2][3]=5<a[1][3]
伪代码
1 2 3 4 5 6 7 8 9 10
| function floyd(map, length) { var i,j,k; for(k for 0 to length){ for(i for 0 to length){ for(j for 0 to length){ map[i][j] = min(map[i][j], map[i][k]+map[k][j]); } } } }
|
单源最短路径Dijkstra算法
假设有一个数组 dis[]
,存储第一个点能到达的所有点的距离,{0,1,12,∞,∞,∞}
,
那么, dis[]
就是 第一个点1
到达所有点的最短距离。
dis[]
为 {0,1,12,∞,∞,∞}
。
从dis[]数组
可以看出, 距离第一个点1
最近的点是第二个点2
, 将其作为中转点。
再从矩阵map[2][j]
中获取所有与第二个点B
邻接的点, 即第三个点3
和第四个点4
,
用dis[j]
加上3、4
和2
的距离,如果小于最小值dis[j]
, 则更新dis[j]
的值。
dis[]
为 {0,1,10,4,∞,∞}
。
再找dis[]数组
的最小点, 因为点2
已经使用过了, 所以选择 点4
作为中转点。
dis[]
为 {0,1,8,4,17,19}
。
以此类推
以点3
作为中转点, dis[]
为 {0,1,8,4,13,19}
。
以点5
作为中转点, dis[]
为 {0,1,8,4,13,17}
。
还剩下最后一个 点6
, 没必要找了, 因为没有另一个点可以到达了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class Main{ private static int MAX_STEP = 1000; public static void main(String[] args) throws Exception { Scanner in = new Scanner(System.in); int n = in.nextInt(); int m = in.nextInt(); int[][] map = new int[n+1][n+1]; boolean[] book = new boolean[n+1]; int[] dis = new int[n+1]; for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++){ if(i==j) map[i][j] = 0; else map[i][j] = MAX_STEP; } } for(int i = 1; i <= m; i++){ map[in.nextInt()][in.nextInt()] = in.nextInt(); } for(int i = 1; i <= n; i++){ dis[i] = map[1][i]; } book[1] = true; for(int i = 1; i <= n-1; i++){ int min = MAX_STEP; int next = 0; for(int j = 1; j <=n; j++){ if(book[j]==false && dis[j]<min){ min = dis[j]; next = j; } } book[next] = true; for(int j = 1; j <= n; j++){ dis[j] = Math.min(dis[j], dis[next]+map[next][j]); } printf(dis); } } private static void printf(int[] arr){ for(int i = 0; i < arr.length; i++){ System.out.print(arr[i]+" "); } System.out.println(""); } }
|
另外还有邻接表存储图的算法(看不懂, 弃了)
单源最短路径Bellman-Ford算法
和Dijkstra算法一样, 假设有一个数组 dis[]
,
存储第一个点能到达的所有点的距离,{0,∞,∞,∞,∞,∞}
,
那么, dis[]
就是 第一个点0
到达所有点的最短距离。
dis[]
为 {0,∞,∞,∞,∞,∞}
, 注意这里和Dijkstra算法不一样。
扫描第一条边0 1 1
, 点0
到 点1
的权值为 1
。
因为 点0
是起点, 所以原先的dis[0]+第一条边的权值w[1]<原先的dis[1]
, 也就是0+1<∞
。
通过第一条边, 可以把 dis[]
更新为{0,1,∞,∞,∞,∞}
。
扫描第二条边0 2 12
, 点0
到 点2
的权值为 12
。
因为 点0
是起点, 所以原先的dis[0]+第二条边的权值w[2]<原先的dis[2]
, 也就是0+12<∞
。
通过第一条边, 可以把 dis[]
更新为{0,1,12,∞,∞,∞}
。
扫描第三条边1 2 9
, 点1
到 点2
的权值为 9
。
因为 点1
是起点, 所以原先的dis[1]+第三条边的权值w[3]<原先的dis[2]
, 也就是1+9<12
。
通过第一条边, 可以把 dis[]
更新为{0,1,10,∞,∞,∞}
。
依次类推,
扫描第4条边1 3 3
, dis[]数组
为 {0,1,10,4,∞,∞}
。
扫描第5条边2 4 5
, dis[]数组
为 {0,1,10,4,15,∞}
。
扫描第6条边3 2 4
, dis[]数组
为 {0,1,8,4,15,∞}
。
扫描第7条边 3 4 13
, dis[]数组
为 {0,1,8,4,15,∞}
。
扫描第8条边 3 5 15
, dis[]数组
为 {0,1,8,4,15,19}
。
扫描第9条边4 5 4
,dis[]数组
为 {0,1,8,4,15,19}
。
第一轮扫描完毕, 一共要扫描 顶点数-1
轮。
因为任意两点之间最多包含顶点数-1
条边。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| public class Main { public static void main(String[] args) {
Scanner in = new Scanner(System.in); int n = in.nextInt(), m = in.nextInt(); int[] dis = new int[n]; int[] u = new int[m]; int[] v = new int[m]; int[] w = new int[m];
for(int i = 0; i < m; i++){ u[i] = in.nextInt(); v[i] = in.nextInt(); w[i] = in.nextInt(); } dis[0] = 0; for(int i = 1; i < n; i++){ dis[i] = 999; }
boolean equals = false; int[] backupDis = new int[m];
for(int k = 0; k < n-1 && !equals; k++){ backup(dis, backupDis); for(int i = 0; i < m; i++){ dis[v[i]] = Math.min(dis[v[i]], dis[u[i]]+w[i]); } equals = equals(dis, backupDis); }
boolean isLoop = false; for(int i = 0; i < m && !isLoop; i++){ if(dis[v[i]] > dis[u[i]]+w[i]){ isLoop = true; } } System.out.println(isLoop?'有':'无'+"负权回路");
if(!isLoop){ System.out.println(Arrays.toString(dis)); }
}
private static void backup(int[] src, int[] backup){ System.arraycopy(src, 0, backup, 0, src.length); }
private static boolean equals(int[] src, int[] other){ if(src.length!=other.length) return false;
for(int i = 0, len = src.length; i < len; i++) { if(src[i]!=other[i]) return false; } return true; } }
|
总结
算法 |
单、多源 |
算法核心 |
空间复杂度 |
时间复杂度 |
适用情况 |
Floyd-Warshall |
多源 |
中转点 |
O(N^2) |
O(N^3) |
稠密图, 解决负权回路 |
Dijkstra |
单源 |
中转点 |
O(M) |
O((M+N)logN) |
稠密图 |
Bellman-Ford |
单源 |
边 |
O(M) |
O(NM) |
稀疏图, 解决负权回路 |