最近在做算法题的时候总是遇到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算法将在多个最短路径中找到最终解
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题都可以套用上述模板很容易地做出来,当然平时做题时还是需要根据具体题目灵活变通,以上代码只是将其思路梳理了一遍,在实现上也依然存在许多可以优化的地方。