程式設計師必須會的基本演算法8-Dijkstra演算法(迪傑斯特拉演算法)

入夢鏡發表於2020-09-26

Dijkstra演算法

迪傑斯特拉(Dijkstra)演算法是典型最短路徑演算法,
用於計算一個結點到其他結點的最短路徑。
它的主要特點是以起始點為中心向外層層擴充套件(廣度優先搜尋思想),直到擴充套件到終點為止。

問題場景

在這裡插入圖片描述

有7個節點(A,B,C,D,E,F,G),
從G點出發,分別走到A,B,C,D,E,F六個節點
各個節點的距離用邊線表示(權),比如A–B距離5
如何計算出G到其它各個節點的最短距離?
如果從其它點出發到各個點的最短距離又是多少?

程式碼解決

package basic;

public class Dijkstra
{
	/*
	 * 看完下面的,我們來總體看下迪傑斯特拉演算法的執行策略
	 * 首先訪問過的只有一個G,然後找到G能訪問的節點中權重最小的
	 * 那就是GA,然後將GA看做一個節點,能訪問到的節點中權重最小的
	 * 那就是GAB,然後又看作一個整體,下一個能訪問到的節點中權重最小的
	 * 那就是GABE,就是這樣的策略
	 */
	public static void main(String[] args)
	{
//		char[] nodes={ 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
//		int[][] weight=new int[nodes.length][nodes.length];
//		final int N=Integer.MAX_VALUE/2;
//		
//		weight[0]=new int[]{N,5,7,N,N,N,2};  
//		weight[1]=new int[]{5,N,N,9,N,N,3};  
//		weight[2]=new int[]{7,N,N,N,8,N,N};  
//		weight[3]=new int[]{N,9,N,N,N,4,N};  
//		weight[4]=new int[]{N,N,8,N,N,5,4};  
//		weight[5]=new int[]{N,N,N,4,5,N,6};  
//		weight[6]=new int[]{2,3,N,N,4,6,N};
		//上面都是初始化資料
		char[] nodes={ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ,'H','I'};
		int[][] weight=new int[nodes.length][nodes.length];
		final int N=Integer.MAX_VALUE/2;		
		
		weight[0]=new int[]{N,5,7,N,N,N,2,N,N};  
		weight[1]=new int[]{5,N,N,9,N,N,3,N,N};  
		weight[2]=new int[]{7,N,N,N,8,N,N,N,N};  
		weight[3]=new int[]{N,9,N,N,N,4,N,2,N};  
		weight[4]=new int[]{N,N,8,N,N,5,4,N,N};  
		weight[5]=new int[]{N,N,N,4,5,N,6,N,N};  
		weight[6]=new int[]{2,3,N,N,4,6,N,N,N};
		weight[7]=new int[]{N,N,N,2,N,N,N,N,2};
		weight[8]=new int[]{N,N,N,N,N,N,N,2,N};
		
		dijkstra(weight, 6);
	}
	public static void dijkstra(int[][] weight,int index)
	{
		//需要三個額外的變數來儲存資料
		//這表示這個節點是否是已經被訪問過了
		int[] vistied=new int[weight.length];
		//這表示路徑是什麼
		String[] paths=new String[weight.length];
		//這表示從出發點到當前節點的距離是多少
		int[] TheLength=new int[weight.length];
		
		//初始化路徑
		for(int x=0;x<paths.length;x++)
		{
			paths[x]=new String(index+"->"+x);
		}
		//初始化出發點
		vistied[index]=1;
		TheLength[index]=0;
		/*
		 * 然後是遍歷n-1個節點,因為第一個節點已經遍歷過了
		 * 這裡每一次都是找和出發點相通的,權重最小的那個節點,
		 * 然後對它進行深一層的遍歷,看看它走到下一層後的最小路徑是否能更加小
		 * 在這裡你可能在想第二重的for迴圈會不會沒有進去,就是那個找MinLength的for迴圈
		 * 然後讓下標temp=-1,其實不會的,在後面的for遍歷下一層的時候會改變下一層圖的遍歷
		 * 就是比如出發點是第零層,然後MinLength是找第二層節點的最小權重,然後下面的for會
		 * 根據第二層的最小權重去找第二層到第三層的最小權重,如果是出發點不能到達的節點
		 * 那麼就肯定在第三層或者更加低的層,那麼在第二層遍歷會修改第三層節點的可達,就是修改權重
		 * 那麼迴圈後再回到上面的尋找下一個最小權重的沒有訪問過的節點的時候就會可能不是從
		 * 第二層找起,而是直接到更加低的層次找
		 */
		for(int x=1;x<weight.length;x++)
		{
			int MinLength=Integer.MAX_VALUE/2;
			int temp=-1;
			for(int y=0;y<weight.length;y++)
			{
				if(vistied[y]==0 && weight[index][y]<MinLength)
				{
					MinLength=weight[index][y];
					temp=y;
				}
			}
			TheLength[temp]=MinLength;
			vistied[temp]=1;
			/**
			 * 首先是出發點到index節點的距離,然後index這個節點能訪問的下一層節點
			 * 如果是從index這個節點出發訪問到的下一層節點的距離加上從出發點到index的距離
			 * 比從出發點到index下一層節點的距離(這是不經過index節點的,從其他的路線出發的)小,
			 * 那麼就將這TheLength的陣列對應的index下一層節點對應的位置進行修改
			 */
			for(int y=0;y<weight.length;y++)
			{
				if(vistied[y]==0 && weight[index][temp]+weight[temp][y]<weight[index][y])
				{
					weight[index][y]=weight[index][temp]+weight[temp][y];
					paths[y]=paths[temp]+"->"+y;
				}
			}
		}
		for(int x=0;x<paths.length;x++)
		{
			if(x!=index)
			{
				System.out.println(index+"到"+x+"的路徑:"+paths[x]+"		權重:"+TheLength[x]);
			}
		}
	}
}

結果

60的路徑:6->0		權重:2
61的路徑:6->1		權重:3
62的路徑:6->0->2		權重:9
63的路徑:6->5->3		權重:10
64的路徑:6->4		權重:4
65的路徑:6->5		權重:6
67的路徑:6->5->3->7		權重:12
68的路徑:6->5->3->7->8		權重:14

注意

1.迪傑斯特拉演算法只支援非負權的圖
比如上面的例子,如果B-D(-90),那麼在遍歷的時候,首先加入A進入已訪問的陣列裡面,然後又將B加入到已訪問陣列裡面,那麼就會確定G到A最小距離是2,到B是3,而不是經過B-D這條線,迪傑斯特拉不會重新計算已經算好最小權重的節點,
2.節點之間不可達用Integer.MAX_VALUE代表
因為兩個Integer.MAX_VALUE相加會溢位導致出現負權

相關文章