最短路径的常用解法有迪杰斯特拉算法(Dijkstra Algorithm)、弗洛伊德算法(Floyd-Warshall Algorithm)和贝尔曼福特算法(Bellman-Ford Algorithm)。
其中,Floyd算法是多源最短路径算法,即求任意点到任意点的最短路径,而Dijkstra算法和Bellman-Ford算法是单源最短路径算法,即单个点到单个点的最短路径。这三种算法还有一点不同,就是Dijkstra算法处理有向权重图时,权重必须为正值,而另外两种可以处理负权重有向图,但是不能出现负环。所谓负环,就是权重均为负的环。为啥呢,这里要先引入松弛操作 Relaxtion,这是这三个算法的核心思想,当有对边 (u, v) 是结点u到结点v,如果 dist(v) > dist(u) + w(u, v),那么 dist(v) 就可以被更新,这是所有这些的算法的核心操作。
一句话形容这个算法:如果P(i,j)={Vi…Vk…Vs…Vj}是从从顶点i到j的最短路径,k和s是这条路径上的中间顶点,那么P(k,s)必定是从k到s的最短路径。
public static int Dijkstra(int[][] graph,int src,int des) {
int n=graph.length;//n个结点
int[] distance=new int[n];
boolean[] visited=new boolean[n];//相当于集合T
for(int i=1;i<distance.length;i++) {
//初始化为不可达
distance[i]=maxValue;
}
distance[src]=0;
visited[src]=true;//将源点加入集合T
for(int i=1;i<n;i++) {
if(graph[src][i]!=maxValue) distance[i]=graph[src][i];
}
for(int i=1;i<n;i++) {
int minValue=Integer.MAX_VALUE;
int node=-1;
for(int j=1;j<distance.length;j++) {
//这一部分可以用优先队列来优化
if(visited[j]==true) continue;
if(distance[j]<minValue) {
minValue=distance[j];
node=j;
}
}
if(node==-1) break;
else visited[node]=true;//加入T集合
for(int k=1;k<graph.length;k++) {
//更新distance数组
if(visited[k]==true) continue;
if(distance[k]>distance[node]+graph[node][k]) {
distance[k]=distance[node]+graph[node][k];
}
}
}
return distance[des];
}
static class Node{
int node;
int distance;
public Node(int node,int distance) {
this.node=node;
this.distance=distance;
}
}
static class NodeComparator implements Comparator<Node>{
@Override
public int compare(Node o1, Node o2) {
return o1.distance-o2.distance;
}
}
public static int priorityQueueDijkstra(int[][] graph,int src,int des) {
Queue<Node> priorityQueue = new PriorityQueue<Node>(new NodeComparator());
int n=graph.length;//n个结点
int[] distance=new int[n];
boolean[] visited=new boolean[n];//相当于集合T
for(int i=1;i<distance.length;i++) {
//初始化为不可达
distance[i]=maxValue;
}
distance[src]=0;
priorityQueue.offer(new Node(src,0));
while(!priorityQueue.isEmpty()) {
Node curNode = priorityQueue.poll();
visited[curNode.node]=true;//将源点加入集合T
for(int i=1;i<graph[curNode.node].length;i++) {
if(visited[i]==true) continue;
else {
if(distance[i] > distance[curNode.node]+graph[curNode.node][i]) {
distance[i] = distance[curNode.node]+graph[curNode.node][i];
priorityQueue.offer(new Node(i,distance[i]));
}
}
}
}
return distance[des];
}
Bellman-Ford 是基于动态规划来求全局最优解的。它的原理是对图进行**{V-1}**次的松驰操作,得到所有可能的最短路径。其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高。高达O(V*E)。但算法可以进行若干种优化,提高了效率。
贝尔曼-福特算法与迪科斯彻算法类似,都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。在两个算法中,计算时每个边之间的估计距离值都比真实值大,并且被新找到路径的最小长度替代。 然而,迪科斯彻算法以贪心法选取未被处理的具有最小权值的节点,然后对其的出边进行松弛操作;而贝尔曼-福特算法简单地对所有边进行松弛操作,共 {|V|-1}次,其中 | V |是图的点的数量。在重复地计算中,已计算得到正确的距离的边的数量不断增加,直到所有边都计算得到了正确的路径。
743. Network Delay Time 采用 Bellman-Ford 解法
// solution 3 经典Bellman-Ford
public int networkDelayTime(int[][] times, int N, int K) {
int maxValue = 6005;
int[] distance = new int[N+1];
for(int i=1;i<distance.length;i++){
distance[i]=maxValue;
}
distance[K]=0;
for(int j=1;j<N;j++){
for(int i=0;i<times.length;i++){
int u=times[i][0],v=times[i][1],w=times[i][2];
i f(distance[v] > distance[u]+w ){
distance[v]=distance[u]+w;
}
}
}
int res=0;
for(int i=1;i<distance.length;i++){
res=Math.max(res,distance[i]);
}
return res==maxValue ? -1 : res;
}
个人觉得,Floyd-Warshall算法是Bellman-Ford算法的升维版本。
Floyd-Warshall算法的原理是动态规划。
维基百科-Floyd-Warshall算法
算法思路:
通过Floyd计算图G=(V,E)中各个顶点的最短路径时,需要引入两个矩阵,矩阵D中的元素a[i]][j]表示顶点i(第i个顶点)到顶点j(第j个顶点)的距离。矩阵P中的元素b[i][j],表示顶点 i到顶点j经过了b[i][j],记录值所表示的顶点。(S是最短距离矩阵,对应Dijkstra算法中的distance数组,P是前一个顶点矩阵用来记录路径,对应Dijkstra算法中的pre数组。)
假设图G中顶点个数为N,则需要对矩阵D和矩阵P进行N次更新。初始时,矩阵D中顶点D[i][j]的距离为顶点i到顶点j的权值;如果i和j不相邻,则D[i][j]=∞,矩阵P的值为顶点b[i][j]的j的值。 接下来开始,对矩阵D进行N次更新。第k次更新时,如果”D[i][j]的距离” > “D[i][k]+a[k][j]”,则更新a[i][j]为”a[i][k]+a[k][j]”,更新b[i][j]=b[i][k]。 更新N次之后,操作完成!
最短路径问题—Dijkstra算法详解
[LeetCode] 743. Network Delay Time 网络延迟时间
最短路径问题—Floyd算法详解