最短路径问题—Dijkstra算法及相关例题

最近在做算法题的时候总是遇到Dijkstra相关的题目,之前虽然学过图论的一些算法,但第一次做这类题时完全不知从何入手。看了一些博客,并且在PAT上折腾了几题后,发现一些常用的模板与套路,因此在这里进行一个总结。关于Dijkstra的理论知识可以参考这篇博客:最短路径问题-Dijkstra算法详解

Dijkstra算法

Dijkstra算法往往和dfs结合在一起考,因此这里给出一个求解基础Dijkstra+dfs相关题目的大致模板:

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

此题要求求出两点之间的最短路径,如果存在多条最短路径,那么就选择点权和最大的路径。这里的代码和上面模板几乎一模一样,做题时都需要考虑点权。

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就完成最大点权和的判断:

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)最小的那条路径。

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来存储城市名与下标的映射即可。

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

你可能感兴趣的:(算法)