图的最短路径

图的最短路径

最短路径的常用解法有迪杰斯特拉算法(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) 就可以被更新,这是所有这些的算法的核心操作。

解决单源最短路径问题

Dijkstra算法(单源最短路径)

一句话形容这个算法:如果P(i,j)={Vi…Vk…Vs…Vj}是从从顶点i到j的最短路径,k和s是这条路径上的中间顶点,那么P(k,s)必定是从k到s的最短路径。

Dijkstra算法思路
  • 策略: Dijkstra算法采用的是一种贪心的策略。
  • 需要的数据结构:声明一个数组disance来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合T(由boolean[] visited实现),若要还原出最终路径还需要一个记录上一个结点的数组prenode
  • 思路:
    -> 1. 初始时,原点s的路径权重被赋为0(distance[s]=0),若对于顶点s能达到的边**(s,m),则把distance[m]设为w**,同时把所有其他s不能直接到达的顶点路径权重设置为无穷大。初始时,集合T中只有顶点s
    ->2. 然后,从distance数组中选择最小值,则该值就是源点s到该顶点的最短路径,并且把该顶点加入到集合T中,便完成了一个顶点。
    ->3. 然后,需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他顶点的路径长度是否比源点直接到达短,如果是,则更新distance数组从源点s到该顶点的距离。
    ->4. 然后,又从distance数组中找出最小值,获取对应的顶点,重复上述动作,直到集合T中包含了图的所有顶点。

Dijkstra常规实现与优先队列优化实现

常规实现
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 算法(单源最短路径,可带负环)

Bellman-Ford 是基于动态规划来求全局最优解的。它的原理是对图进行**{V-1}**次的松驰操作,得到所有可能的最短路径。其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高。高达O(V*E)。但算法可以进行若干种优化,提高了效率。

贝尔曼-福特算法与迪科斯彻算法类似,都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。在两个算法中,计算时每个边之间的估计距离值都比真实值大,并且被新找到路径的最小长度替代。 然而,迪科斯彻算法以贪心法选取未被处理的具有最小权值的节点,然后对其的出边进行松弛操作;而贝尔曼-福特算法简单地对所有边进行松弛操作,共 {|V|-1}次,其中 | V |是图的点的数量。在重复地计算中,已计算得到正确的距离的边的数量不断增加,直到所有边都计算得到了正确的路径。

743. Network Delay Time 采用 Bellman-Ford 解法

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算法(多源最短路径)

个人觉得,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算法详解

你可能感兴趣的:(数据结构与算法,算法)