在上一節的時候曾說過了圖的兩種遍歷方式,在這一節將使用他們作更深層的應用,研究從一個點到另外一個點的最短距離。算法
基本思想是,按照非遞減的順序,找出各個點的最短路。
很容易想到按照非遞減的順序,也就是優先從原點開始,不斷的計算與他相距最近的點的距離,整個的過程就是一個BFS。
在bfs的過程當中,咱們以前是用一個布爾類型的數組來保存一個節點是否被訪問。如今咱們能夠將其改爲爲int類型的二維數組,同時該數組還須要實現兩個功能,對於第I個節點Vertex i,p[ i ][ 0 ]用於保存它到原點的距離,而p[ i ][ 1 ]用於保存最短路徑中他被哪一個節點所指。在輸出最短路徑時,咱們只須要沿着它的路徑倒着走直到原點,反一下就是正確的路徑了。數組
//無全權圖最短路徑 int p[MAXSIZE][2]; void UnweightShortestPath(Graph G, Vertex x) { for (int i = 0; i < MAXSIZE; i++) { memset(p[i], -1, sizeof(p[i])); } //本身到本身的距離是0 p[x][0] = 0; Queue Q = makeempty(); QueueAdd(x, Q); while (!isEmpty(Q)) { Vertex v = QueueDelete(Q); for (int i = 0; i < G->Nvertex; i++) { if (G->graph[v][i] != 0 && p[i][0]==-1) { p[i][0] = p[v][0] + 1; p[i][1] = v; QueueAdd(i, Q); } } } int end; scanf_s("%d", &end); stack<int> v; }
Dijkstra算法的基本算法思想是:建立一個集合S,不斷的向該集合中添加頂點,每次移出時肯定了該頂點的最短的。隨着不斷的添加,移除使得全部頂點的最短路徑都被肯定爲最小。(貪心算法的思想)編輯器
具體實現是首先從原點開始,將其標記爲已讀(表明該點在整個圖中最短路徑肯定 ),將它的全部鏈接點加入集合中,並將這些點到原點的最短距離設置爲權值,並標記他們指向原點(在肯定了最短路徑打印時須要用)。spa
隨後從集合中移除距離最小的一個,將該節點標記爲已讀(該點最短路徑必定已經肯定,由於他是原點的鏈接點中距離最短的那個,若是從原點通過其餘的節點到他,距離一定會更大)。code
將該節點的鄰接點加入集合,並將這些點到原點的最短距離設置爲移除點的最短距離加上自身到移除點的距離和本身原來最短距離中的最小值。
即\(dp[ i ] = Min\){\(dp[ i ] ,dp[ v ]+weight[ v ][ i ]\)}(\(dp[ i ]\)表示i點到遠點的最短距離\(weight[ v ][ i ]\)v到i的距離)。orm
遞歸的則運行直到全部的點都被集合收入又移出結束。blog
注意到:在上面的算法中咱們是不斷的距離從近到遠,在圖中若是有負值出現的話,咱們將會在這個負值圈內打轉。該算法再存在負值的權值是無效的。遞歸
#define INFINITY 10000000 //不一樣的編輯器該值並不相同,可能會報致使值爲0 //find MinDis Vertex findMin(Graph G,int dis[],bool collection[]) { Vertex minV = 0; int minDis = INFINITY; for (Vertex i = 0; i < G->Nvertex; i++) { if (collection[i] == false && dis[i] < minDis) { minDis = dis[i]; minV = i; } } //若是最短距離被改變,說明有找到 if (minDis < INFINITY) { return minV; } return notFound; } //Dijkstra //注意,在該有權值圖中,應是無邊鏈接的兩點爲正無窮大 bool Dijkstra(Graph G, Vertex x) { //save information int shortDis[MAXSIZE]; bool collection[MAXSIZE]; int path[MAXSIZE]; memset(shortDis, INFINITY, sizeof(shortDis)); memset(collection, false, sizeof(collection)); memset(path, -1, sizeof(path)); //先將起始點的鏈接點距離初始化 for (Vertex i = 0; i <= G->Nvertex; i++) { shortDis[i] = G->graph[x][i]; if (shortDis[i] < INFINITY) { path[i] = x; } } collection[x] = true; shortDis[x] = 0; while (true) { Vertex v = findMin(G, shortDis, collection); if (v == notFound) { break; } collection[v] = true; for (Vertex i = 0; i <= G->Nvertex; i++) { if (G->graph[v][i] < INFINITY && collection[i] == false) { //存在負值圈算法沒法正常進行 if (G->graph[v][i] < 0) { return false; } /* * 移除點的最短距離加上自身到移除點的距離小於本身原來最短距離。 */ if (shortDis[i] > shortDis[v] + G->graph[v][i]) { shortDis[i] = shortDis[v] + G->graph[v][i]; path[i] = v; } } } } return true; }
在上面的方法中爲了求某一點到與固定點的最短距離,使用Dijkstra算法,如今咱們要求任意兩點間的最短距離,咱們固然能夠將全部的點都用一次Dijkstra算法來獲得,之後更簡單更快的方法是使用Floyd算法。io
Floyd算法的算法思路是:
用一個二維數組dp[ ][ ]來保存第I個點到第J這個點的最短距離。
每次循環嘗試在I到J的路徑中,加入第K個節點(k = 1.....N)試試會不會讓路徑變短,若是路徑會變短即(\(dp[ i ][ j ]<dp[ i ][ k ]+dp[ k ][ j ]\)),就更新\(dp[ i ][ j ]=dp[ i ][ k ]+dp[ k ][ j ]\),不然不變。通過K輪循環以後,咱們將會獲得全部的任意兩點間的最短路徑一樣的 。form
該算法如何進行:
初始的時候咱們令數組保存的是原來圖中兩點的距離,而且將本身指向本身的距離設爲1。在插入第1個節點時(在程序中下標爲0),咱們注意到在起始點或結束點有該第一個節點的話,是不會改變的,即\(dp[ i ][ 0 ]==dp[ i ][ 0 ]+dp[ 0 ][ 0 ]\)或\(dp[ 0 ][ j ]==dp[ 0 ][ 0 ]+dp[ 0 ][ j ]\),注意到\(dp[ i ][ j ] = Min(dp[ i ][ 0 ]+dp[ 0 ][ j ],dp[ i ][ j ])\)。循環着往下走咱們發現,每次\(dp[ i ][ j ]\)的值改變,必定是由第K行和第K列決定,而該列在此輪外循環中,值絕對不會發生改變。
同上面的算法同樣該算法在存在負值圈的時候,也會出問題。
bool Floyd(Graph G) { int dp[MAXSIZE][MAXSIZE]; int path[MAXSIZE][MAXSIZE]; for (int i = 0; i <= G->Nvertex; i++) { for (int j = 0; j <= G->Nvertex; j++) { if (i == j) { dp[i][j] = 0; } else { dp[i][j] = G->graph[i][j]; } } } for (int i = 0; i <= G->Nvertex; i++) { for (int j = 0; j <= G->Nvertex; j++) { for (int k = 0; k <= G->Nvertex; k++) { if (dp[i][j] > dp[i][k] + dp[k][j]) { dp[i][j] = dp[i][k] + dp[k][j]; if (i == j && dp[i][j] < 0) { return false; } path[i][j] = k; } } } } return true; }