最近在做算法题的时候总是遇到Dijkstra相关的题目,之前虽然学过图论的一些算法,但第一次做这类题时完全不知从何入手。看了一些博客,并且在PAT上折腾了几题后,发现一些常用的模板与套路,因此在这里进行一个总结。关于Dijkstra的理论知识可以参考这篇博客:最短路径问题-Dijkstra算法详解
Dijkstra算法
Dijkstra算法往往和dfs结合在一起考,因此这里给出一个求解基础Dijkstra+dfs相关题目的大致模板:
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 73 74 75 76 77 78 79 80 81 82 83 84 85
| public class Main { static int n; static int m; static int C1; static int C2; static int[][] e; static int[] weight; static int[] dis; static boolean[] visit; static ArrayList<Integer>[] pre; static LinkedList<Integer> tempPath = new LinkedList<Integer>(); static LinkedList<Integer> path = new LinkedList<Integer>();
public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); m = sc.nextInt(); C1 = sc.nextInt(); C2 = sc.nextInt(); visit = new boolean[n]; weight = new int[n]; for(int i = 0; i < n; i++) { weight[i] = sc.nextInt(); } e = new int[n][n]; for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { e[i][j] = e[j][i] = Integer.MAX_VALUE; } } for(int i = 0; i < m; i++) { int c1 = sc.nextInt(); int c2 = sc.nextInt(); e[c1][c2] = e[c2][c1] = sc.nextInt(); } dis = new int[n]; for(int i = 0; i < n; i++) { dis[i] = Integer.MAX_VALUE; } dis[C1] = 0; pre = new ArrayList[n]; for(int i = 0; i < n; i++) { pre[i] = new ArrayList<>(); } for(int i = 0; i < n; i++) { int u = -1, min = Integer.MAX_VALUE; for(int j = 0; j < n; j++) { if(!visit[j] && dis[j] < min) { min = dis[j]; u = j; } } if(u == -1) break; visit[u] = true; for(int v = 0; v < n; v++) { if(!visit[v] && e[u][v] != Integer.MAX_VALUE) { if(dis[v] > dis[u] + e[u][v]) { dis[v] = dis[u] + e[u][v]; pre[v].clear(); pre[v].add(u); } else if(dis[v] == dis[u] + e[u][v]) { pre[v].add(u); } } } } dfs(C2); }
private static void dfs(int v) { tempPath.push(v); if(v == C1) { tempPath.pop(); return; } for(int i = 0; i < pre[v].size(); i++) dfs(pre[v].get(i)); tempPath.pop(); } }
|
Emergency
题目链接:1003 Emergency
此题要求求出两点之间的最短路径,如果存在多条最短路径,那么就选择点权和最大的路径。这里的代码和上面模板几乎一模一样,做题时都需要考虑点权。
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| public class Main { private static int n; private static int m; private static int C1; private static int C2; private static int[][] e; private static int[] weight; private static int[] dis; private static boolean[] visit; private static int max = Integer.MIN_VALUE; private static ArrayList<Integer>[] pre; private static LinkedList<Integer> tempPath = new LinkedList<Integer>(); private static LinkedList<Integer> path = new LinkedList<Integer>(); private static int cnt = 0; public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); m = sc.nextInt(); C1 = sc.nextInt(); C2 = sc.nextInt(); visit = new boolean[n]; weight = new int[n]; for(int i = 0; i < n; i++) { weight[i] = sc.nextInt(); } e = new int[n][n]; for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { e[i][j] = e[j][i] = Integer.MAX_VALUE; } } for(int i = 0; i < m; i++) { int c1 = sc.nextInt(); int c2 = sc.nextInt(); e[c1][c2] = e[c2][c1] = sc.nextInt(); } dis = new int[n]; for(int i = 0; i < n; i++) { dis[i] = Integer.MAX_VALUE; } dis[C1] = 0; pre = new ArrayList[n]; for(int i = 0; i < n; i++) { pre[i] = new ArrayList<>(); } for(int i = 0; i < n; i++) { int u = -1, min = Integer.MAX_VALUE; for(int j = 0; j < n; j++) { if(!visit[j] && dis[j] < min) { min = dis[j]; u = j; } } if(u == -1) break; visit[u] = true; for(int v = 0; v < n; v++) { if(!visit[v] && e[u][v] != Integer.MAX_VALUE) { if(dis[v] > dis[u] + e[u][v]) { dis[v] = dis[u] + e[u][v]; pre[v].clear(); pre[v].add(u); } else if(dis[v] == dis[u] + e[u][v]) { pre[v].add(u); } } } } dfs(C2); System.out.printf("%d %d", cnt, max); } private static void dfs(int v) { tempPath.push(v); if(v == C1) { int a = 0; for(int i = 0; i < tempPath.size(); i++) { a += weight[tempPath.get(i)]; } if(a > max) { max = a; path = new LinkedList<>(tempPath); } cnt++; tempPath.pop(); return; } for(int i = 0; i < pre[v].size(); i++) dfs(pre[v].get(i)); tempPath.pop(); } }
|
事实上,我们也可以不使用DFS,而在执行Dijkstra就完成最大点权和的判断:
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 73
| public class Main { private static int n; private static int m; private static int c1; private static int c2; private static int[][] e; private static int[] dis; private static int[] nums; private static int[] w; private static int[] weight; private static boolean[] visit; public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); m = sc.nextInt(); c1 = sc.nextInt(); c2 = sc.nextInt(); weight = new int[n]; e = new int[n][n]; dis = new int[n]; nums = new int[n]; w = new int[n]; visit = new boolean[n]; for(int i = 0; i < n; i++) { weight[i] = sc.nextInt(); } for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { e[i][j] = Integer.MAX_VALUE; } } for(int i = 0; i < m; i++) { int s = Integer.valueOf(sc.nextInt()); int d = Integer.valueOf(sc.nextInt()); e[s][d] = e[d][s] = Integer.valueOf(sc.nextInt()); } for(int i = 0; i < n; i++) { dis[i] = Integer.MAX_VALUE; } dis[c1] = 0; nums[c1] = 1; w[c1] = weight[c1]; for(int i = 0; i < n; i++) { int u = -1, min = Integer.MAX_VALUE; for(int j = 0; j < n; j++) { if(!visit[j] && dis[j] < min) { u = j; min = dis[j]; } } if(u == -1) break; visit[u] = true; for(int v = 0; v < n; v++) { if(!visit[v] && e[u][v] != Integer.MAX_VALUE) { if(dis[u] + e[u][v] < dis[v]) { dis[v] = dis[u] + e[u][v]; nums[v] = nums[u]; w[v] = w[u] + weight[v]; } else if(dis[u] + e[u][v] == dis[v]) { nums[v] += nums[u]; w[v] = w[u] + weight[v] > w[v] ? w[u] + weight[v] : w[v]; } } } } System.out.printf("%d %d", nums[c2], w[c2]); } }
|
Travel Plan
题目链接:1030 Travel Plan
这题对比上题是将点权换成了边权,先通过Dijkstra算法求出多条最短路径,然后用DFS找到最短路径中边权(此题中就是cost)最小的那条路径。
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| public class Main { private static int n; private static int m; private static int s; private static int d; private static int[][] e; private static int[][] cost; private static int[] dis; private static boolean[] visit; private static ArrayList<Integer>[] pre; private static LinkedList<Integer> tempPath = new LinkedList<>(); private static LinkedList<Integer> path = new LinkedList<>(); private static int min = Integer.MAX_VALUE; public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); m = sc.nextInt(); s = sc.nextInt(); d = sc.nextInt(); visit = new boolean[n]; e = new int[n][n]; cost = new int[n][n]; for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { e[i][j] = e[j][i] = Integer.MAX_VALUE; cost[i][j] = cost[j][i] = Integer.MAX_VALUE; } } for(int i = 0; i < m; i++) { int i1 = sc.nextInt(); int i2 = sc.nextInt(); e[i1][i2] = e[i2][i1] = sc.nextInt(); cost[i1][i2] = cost[i2][i1] = sc.nextInt(); } dis = new int[n]; for(int i = 0; i < n; i++) { dis[i] = Integer.MAX_VALUE; } dis[s] = 0; pre = new ArrayList[n]; for(int i = 0; i < n; i++) { pre[i] = new ArrayList<>(); } for(int i = 0; i < n; i++) { int u = -1, min = Integer.MAX_VALUE; for(int j = 0; j < n; j++) { if(!visit[j] && dis[j] < min) { min = dis[j]; u = j; } } if(u == -1) break; visit[u] = true; for(int v = 0; v < n; v++) { if(!visit[v] && e[u][v] != Integer.MAX_VALUE) { if(dis[v] > dis[u] + e[u][v]) { dis[v] = dis[u] + e[u][v]; pre[v].clear(); pre[v].add(u); } else if(dis[v] == dis[u] + e[u][v]) { pre[v].add(u); } } } } ArrayList<Integer>[] temppre = pre; dfs(d); for(int i = 0; i < path.size(); i++) { System.out.print(path.get(i) + " "); } System.out.print(dis[d] + " "); System.out.print(min); } private static void dfs(int v) { tempPath.push(v); if(v == s) { int c = 0; for(int i = 1; i < tempPath.size(); i++) { c += cost[tempPath.get(i)][tempPath.get(i-1)]; } if(c < min) { min = c; path = new LinkedList<>(tempPath); } tempPath.pop(); return; } for(int i = 0; i < pre[v].size(); i++) dfs(pre[v].get(i)); tempPath.pop(); } }
|
All Roads Lead to Rome
题目链接:1087 All Roads Lead to Rome
这题和上面两题也没什么不同,基本思路是一样的,只不过题目输入的是城市的名称也就是字符串,并且输出也要用城市的名称,我们直接用map来存储城市名与下标的映射即可。
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| public class Main { private static int[][] e; private static int[] dis; private static int[] weight; private static boolean[] visit; private static int n; private static int k; private static HashMap<Integer, String> map1; private static HashMap<String, Integer> map2; private static LinkedList<Integer> path = new LinkedList<>(); private static LinkedList<Integer> tempPath = new LinkedList<>(); private static ArrayList<Integer>[] pre; private static int max = Integer.MIN_VALUE; private static int avg = Integer.MIN_VALUE; private static int cnt = 0; public static void main(String[] args) { Scanner sc = new Scanner(System.in); String[] line1 = sc.nextLine().split(" "); n = Integer.valueOf(line1[0]); k = Integer.valueOf(line1[1]); map1 = new HashMap<>(); map2 = new HashMap<>(); map1.put(0, line1[2]); map2.put(line1[2], 0); weight = new int[n]; for(int i = 1; i < n; i++) { String[] line = sc.nextLine().split(" "); map1.put(i, line[0]); map2.put(line[0], i); weight[i] = Integer.valueOf(line[1]); } e = new int[n][n]; for(int i = 0; i < e.length; i++) { for(int j = 0; j < e.length; j++) { e[i][j] = e[j][i] = Integer.MAX_VALUE; } } for(int i = 0; i < k; i++) { String[] line = sc.nextLine().split(" "); int c1 = map2.get(line[0]); int c2 = map2.get(line[1]); e[c1][c2] = e[c2][c1] = Integer.valueOf(line[2]); } dis = new int[n]; dis[0] = 0; for(int i = 1; i < n; i++) { dis[i] = Integer.MAX_VALUE; } visit = new boolean[n]; pre = new ArrayList[n]; for(int i = 0; i < n; i++) { pre[i] = new ArrayList<>(); } for(int i = 0; i < n; i++) { int u = -1, min = Integer.MAX_VALUE; for(int j = 0; j < n; j++) { if(!visit[j] && dis[j] < min) { u = j; min = dis[j]; } } if(u == -1) break; visit[u] = true; for(int v = 0; v < n; v++) { if(!visit[v] && e[u][v] != Integer.MAX_VALUE) { if(dis[u] + e[u][v] < dis[v]) { dis[v] = dis[u] + e[u][v]; pre[v].clear(); pre[v].add(u); } else if(dis[u] + e[u][v] == dis[v]) { pre[v].add(u); } } } } dfs(map2.get("ROM")); System.out.printf("%d %d %d %d\n", cnt, dis[map2.get("ROM")], max, avg); System.out.print(map1.get(0)); for(int i = 1; i < path.size(); i++) { System.out.print("->" + map1.get(path.get(i))); } } private static void dfs(int v) { tempPath.push(v); if(v == 0) { int happy = 0; int average = 0; for(int i = 1; i < tempPath.size(); i++) { happy += weight[tempPath.get(i)]; } average = happy / (tempPath.size()-1); if(happy > max) { max = happy; avg = average; path = new LinkedList<>(tempPath); } else if(happy == max && average > avg) { avg = average; path = new LinkedList<>(tempPath); } cnt++; tempPath.pop(); return; } for(int i = 0; i < pre[v].size(); i++) dfs(pre[v].get(i)); tempPath.pop(); } }
|
至此,简单的Dijkstra题都可以套用上述模板很容易地做出来,当然平时做题时还是需要根据具体题目灵活变通,以上代码只是将其思路梳理了一遍,在实现上也依然存在许多可以优化的地方。