一、问题
有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = [fromi, toi, pricei] ,表示该航班都从城市 fromi 开始,以价格 pricei 抵达 toi 。
现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到出一条最多经过 k 站中转的路线,使得从 src 到 dst 的 价格最便宜 ,并返回该价格。 如果不存在这样的路线,则输出 -1。
二、思路
方法一:迪杰斯特拉算法
与 Dijkstra 算法的不同:限制了可以中转 K
次。我们来看下面的例子。
用一个和dist等长的数组stops去记录这个最优价格dist[i]所对应的剩余中转站个数。如果weight_i + dist >= dist[i],我们要看一下当前剩余的中转站次数是否大于stops[i],如果是的话我们依然要将节点i入栈。这样如果最优距离对应的路是一条死胡同,我们也可以检验现在的路。
class Solution {
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
// Build the graph
int mat[][] = new int[n][n];
for (int flight[] : flights) {
mat[flight[0]][flight[1]] = flight[2];
}
// min heap: {(vertex, cost, stops), ...}
PriorityQueue<int[]> minHeap = new PriorityQueue<>((e1, e2) -> e1[1] - e2[1]);
// costs[i]: min cost from src to vertex i
// stops[i]: number of stops of the corresponding cheapest cost for vertex i
int costs[] = new int[n];
int stops[] = new int[n];
Arrays.fill(costs, Integer.MAX_VALUE);
// Dijkstra Algorithm within k
minHeap.offer(new int[] {src, 0, k});
while (!minHeap.isEmpty()) {
int elem[] = minHeap.poll();
int vertex = elem[0], cost = elem[1], stop = elem[2];
if (vertex == dst) {
return cost;
} else if (stop < 0) {
continue;
}
for (int i = 0; i < n; ++i) {
if (mat[vertex][i] > 0) {
int costI = costs[i], costVI = mat[vertex][i];
if (cost + costVI < costI) {
minHeap.offer(new int[] {i, costVI + cost, stop - 1});
costs[i] = costVI + cost;
stops[i] = stop - 1;
} else if (stops[i] < stop - 1) {
minHeap.offer(new int[] {i, costVI + cost, stop - 1});
}
}
}
}
return -1;
}
}
最短路径,是指两顶点之间经过的边上权值之和最少的路径。并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。
关于最短路径主要有两种算法:迪杰斯特拉(Dijkstra) 算法和弗洛伊德(Floyd) 算法。
三、迪杰斯特拉算法
计算的是 从某个源点到其余各顶点的最短路径。具体步骤如下:
- 先将源点v0加入顶点集S,计算v0到其他顶点的距离(此时S中只有v0,故不存在中转顶点);
- 若v0到达顶点vk的距离最小,则将vk加入到集合S中,此时多了一个中转顶点vk;
- 所以要对剩余的各个顶点的最短路径长度进行更新。
- 原来v0到vi的最短路径长度为D[i],加入k作为中间顶点的中转路径长度为:D[k]+Garcs[k][i],若D[k]+Garcs[k][i]<D[i],则用D[k]+Garcs[k][i]取代D[i]。
- 更新后,再选择数组D中值最小的顶点加入到顶点集S中,如此进行下去,直至图中所有顶点加入到顶点集S为止。
四、弗洛伊德算法
计算的是 每个顶点之间的最短路径。