引言java
假如你有一張地圖,地圖上給出了每一對相鄰城市的距離,從一個地點到另一個地點,如何找到一條最短的路? 最短路算法要解決的就是這類問題。今年的華爲精英挑戰賽codeCraft中關於部署大數據下網絡服務器部署問題就須要使用最短路算法,由於求最小流最大費用算法時, 最核心的就是找出最短路,可見最短路算法的應用之普遍。算法
一.定義:數組
給定一個有(無)向圖,每一條邊有一個權值 w,給定一個起始點 S 和終止點 T ,求從 S 出發走到 T 的權值最小路徑,即爲最短路徑。最短路算法依賴一種性質:一條兩頂點間的最短路徑包含路徑上其餘最短路徑。簡單的說就是:最短路徑的子路徑是最短路徑。服務器
二.最短路算法網絡
最經常使用的最短路算法是Dijkstra算法、A*算法、SPFA算法、Bellman-Ford算法和Floyd-Warshall算法,咱們這裏重點介紹並實現Dijkstra和SPFA,以及A*算法,其餘算法只介紹原理,不展開app
1.鬆弛技術(Relaxation)(很是重要)大數據
鬆弛操做的原理是著名的定理:「三角形兩邊之和大於第三邊」,在信息學中咱們叫它三角不等式。所謂對結點i,j進行鬆弛,就是斷定是否dis[j]>dis[i]+w[i,j],若是該式成立則將dis[j]減少到dis[i]+w[i,j],不然不動。ui
2.Dijkstra算法this
解決最短路問題,最經典的算法是 Dijkstra算法,它是一種單源最短路算法,其核心思想是貪心算法(Greedy Algorithm),Dijkstra算法由荷蘭計算機科學家Dijkstra發現,這個算法至今差很少已有50年曆史,可是由於它的穩定性和通俗性,到如今依然強健。另外,Dijkstra算法要求全部邊的權值非負。spa
1) Dijkstra算法思想爲:
設 G = (V, E) 是一個帶權有向圖,把圖中頂點集合 V 分紅兩組,第一組爲已求出最短路徑的頂點集合(用 S 表示,初始時 S 中只有一個源點,之後每求得一條最短路徑 , 就將其加入到集合 S 中,直到所有頂點都加入到 S 中,算法就結束了),第二組爲其他未肯定最短路徑的頂點集合(用 U 表示),按最短路徑長度的遞增次序依次把第二組的頂點加入 S 中。在加入的過程當中,總保持從源點 v 到 S 中各頂點的最短路徑長度不大於從源點 v 到 U 中任何頂點的最短路徑長度。此外,每一個頂點對應一個距離,S 中的頂點的距離就是從 v 到此頂點的最短路徑長度,U 中的頂點的距離,是從 v 到此頂點只包括 S 中的頂點爲中間頂點的當前最短路徑長度。
2)算法核心步驟以下:
a. 將全部頂點分爲兩部分:已知最短路程的頂點集合P和未知最短路徑的頂點集合Q。最開始,已知最短路徑的頂點集合P中只有源點一個頂點。咱們這裏用一個isVisited數組來記錄哪些頂點再集合P中。例如對於某個頂點i,若是isVisited[i]爲1則表示這個頂點再集合P中,若是isVisited[i]爲0則表示這個頂點再集合Q中。
b. 設置源點s到本身的最短路徑爲0即 dis[s]=0。若存在有源點能直接到達的頂點i,則把dis[i]設爲w[s][i]。同時把全部其餘(源點不能直接到達的)頂點的最短路徑設爲∞。
c. 在集合Q的全部頂點中選擇一個離源點s最近的頂點u(即dis[u]最小)加入到集合P。並考察全部以點u爲起點的邊,對每一條邊進行鬆弛操做。例如存在一條從u到v的邊,那麼能夠經過將u->v添加到尾部來拓展一條從s到v的路徑,這條路徑的長度時dis[u]+w[u][v]。若是這個值比目前已知的dis[v]的值要小,咱們能夠用新值來替代當前dis[v]中的值。
d. 重複第3步,若是集合Q爲空,算法結束。最終dis數組中的值就是源點到全部頂點的最短路徑。
補充:dis數組用來記錄起點到全部頂點的距離,Path[]數組,Path[i]表示從S到i的最短路徑中,結點i以前的結點的編號。注意,是「以前」,不是「以後」。最短路徑算法的核心思想成爲「鬆弛」,原理是三角形不等式,咱們只須要在藉助結點u對結點v進行鬆弛的同時,標記下Path[v] = u,記錄的工做就完成了。
3)算法案例圖解
計算從源頂點1到其它頂點間的最短路徑,對下圖中的有向圖,應用Dijkstra算法計算從源頂點1到其它頂點間最短路徑的過程列在下面的表中。
Dijkstra算法的迭代過程:
三.最短路算法Java實現
1.頂點結構
public class Vertex { //存放點信息 public int data; //與該點鄰接的第一個邊節點 public Edge firstEdge; }
2.邊結構
//邊節點 public class Edge { //對應的點下表 public int vertexId; //邊的權重 public int weight; //下一個邊節點 public Edge next; //getter and setter自行補充 }
3.圖結構
import java.util.*; public class graph { public Vertex[] vertexList; //存放點的集合 public graph(int vertexNum){ this.vertexNum=vertexNum; vertexList=new Vertex[vertexNum]; } //點個數 public int vertexNum; //邊個數 public int edgeLength; public void initVertext(){ for(int i=0;i<vertexNum;i++){ Vertex vertext=new Vertex(); vertext.firstEdge=null; vertexList[i]=vertext; //System.out.println("i"+vertexList[i]); } } //針對x節點添加邊節點y public void addEdge(int x,int y,int weight){ Edge edge=new Edge(); edge.setVertexId(y); edge.setWeight(weight); //第一個邊節點 if(null==vertexList[x].firstEdge){ vertexList[x].firstEdge=edge; edge.setNext(null); } //不是第一個邊節點,則採用頭插法 else{ edge.next=vertexList[x].firstEdge; vertexList[x].firstEdge=edge; } }
4.Dijkstra算法
package MSP; import java.util.*; public class Dijkstra { public graph gh; public Dijkstra(graph gh){ this.gh=gh; } //未求出最短路徑的點集合 public ArrayList<Integer> unVisited=new ArrayList(); //已求出最短路徑的點集合 public ArrayList hasVisited=new ArrayList(); //記錄從起點到其餘任意一點的路徑長度 public int distances[]; //記錄Path[i]表示從S到i的最短路徑中,結點i以前的結點的編號,即對應點的前一個節點 public int paths[]; /** * 初始化各點距離及路徑 */ public void init(int x,int y ){ distances=new int[gh.vertexNum]; paths=new int[gh.vertexNum]; for(int i=0;i<distances.length;i++){ distances[i]=Integer.MAX_VALUE; } distances[x]=0; //把與x相鄰的點的距離求出,並標準初始路徑 for(Edge edge=gh.vertexList[x].firstEdge;edge!=null;edge=edge.next){ distances[edge.vertexId]=edge.weight; paths[edge.vertexId]=x; } //初始化未知最短路點集合和已知最短路集合 unVisited.clear(); hasVisited.clear(); hasVisited.add(x); //其他點爲未知 for(int i=0;i<gh.vertexList.length;i++){ if(i!=x){ unVisited.add(i); } } } /** * 求從x到y的最短路徑,並返回該路徑 * @param x * @param y */ public void Dijkstra(int x,int y){ init(x,y); //若是 System.out.println("開始執行..."); while(!unVisited.isEmpty()){ int vertexId=pickMinInUnvisited(x); if(vertexId==-1) break; //將其加入到已hasvisited集合中,並從未訪問列表中去除 hasVisited.add(vertexId); unVisited.remove((Integer)vertexId); //對u爲起點,相鄰的點爲終點的臨接點進行鬆弛操做 relax(vertexId); } for(int i=0;i<distances.length;i++){ System.out.println(x+"-->"+i+"距離爲"+distances[i]); } ArrayList mypath=printPath(x,y); StringBuilder sb=new StringBuilder(); sb.append("路徑爲:"); for(int i=0;i<mypath.size();i++){ sb.append(mypath.get(i)+"-->"); } sb.delete(sb.length()-3,sb.length()); System.out.println(sb.toString()); } /** * 求出從x到y的路徑,因爲path中存放的該點的前一個點的位置 * @param x * @param y */ public ArrayList<String> printPath(int x,int y){ ArrayList mypaths=new ArrayList(); while(y!=x){ mypaths.add(y); y=paths[y]; } mypaths.add(x); //路徑倒置,須要反置回來 Collections.reverse(mypaths); return mypaths; } /** * 考察全部以點u爲起點的邊,對每一條邊進行鬆弛操做。 * @param u */ public void relax(int u){ for(Edge edge=gh.vertexList[u].firstEdge;edge!=null;edge=edge.next){ int v=edge.vertexId; //對v進行鬆弛,看是否知足distances[v]>distances[u]+w[u][v] int w=edge.weight; if(distances[v]>distances[u]+w){ distances[v]=distances[u]+w; //記錄v的最短路徑時,前一個節點爲u paths[v]=u; } } } /** * 從未求出最短路徑的集合中找到與原點最近的點 * @param x */ public int pickMinInUnvisited(int x){ int minIndex=-1; int min=Integer.MAX_VALUE; for(int i=0;i<distances.length;i++){ if(unVisited.contains(i)){ if(distances[i]<min){ minIndex=i; min=distances[i]; } } } return minIndex; } public static void main(String[]args){ graph g=new graph(5); g.initVertext(); g.addEdge(0,1,2); g.addEdge(0,2,2); g.addEdge(1,4,1); g.addEdge(1,3,3); g.addEdge(2,3,3); g.addEdge(4,3,1); Dijkstra dj=new Dijkstra(g); dj.Dijkstra(1,3); } }
執行結果以下:
1-->0距離爲2147483647
1-->1距離爲0
1-->2距離爲2147483647
1-->3距離爲2
1-->4距離爲1
路徑爲:1-->4-->3