一、狄傑斯特拉算法介紹
迪傑斯特拉(Dijkstra)算法是典型最短路徑算法,用於計算一個節點到其他節點的最短路徑。
它的主要特點是以起始點為中心向外層層擴展(廣度優先搜索思想),直到擴展到終點為止。
基本思想
通過Dijkstra計算圖G中的最短路徑時,需要指定起點s(即從頂點s開始計算)。
此外,引進兩個集合S和U。S的作用是記錄已求出最短路徑的頂點(以及相應的最短路徑長度),而U則是記錄還未求出最短路徑的頂點(以及該頂點到起點s的距離)。
初始時,S中只有起點s;U中是除s之外的頂點,並且U中頂點的路徑是"起點s到該頂點的路徑"。然后,從U中找出路徑最短的頂點,並將其加入到S中;接着,更新U中的頂點和頂點對應的路徑。 然后,再從U中找出路徑最短的頂點,並將其加入到S中;接着,更新U中的頂點和頂點對應的路徑。 ... 重復該操作,直到遍歷完所有頂點。
操作步驟
(1) 初始時,S只包含起點s;U包含除s外的其他頂點,且U中頂點的距離為"起點s到該頂點的距離"[例如,U中頂點v的距離為(s,v)的長度,然后s和v不相鄰,則v的距離為∞]。
(2) 從U中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從U中移除頂點k。
(3) 更新U中各個頂點到起點s的距離。之所以更新U中頂點的距離,是由於上一步中確定了k是求出最短路徑的頂點,從而可以利用k來更新其它頂點的距離;例如,(s,v)的距離可能大於(s,k)+(k,v)的距離。
(4) 重復步驟(2)和(3),直到遍歷完所有頂點。
單純的看上面的理論可能比較難以理解,下面通過實例來對該算法進行說明。
二、狄傑斯特拉算法圖解
以上圖G4為例,來對迪傑斯特拉進行算法演示(以第4個頂點D為起點)。
初始狀態:S是已計算出最短路徑的頂點集合,U是未計算除最短路徑的頂點的集合!
第1步:將頂點D加入到S中。
此時,S={D(0)}, U={A(∞),B(∞),C(3),E(4),F(∞),G(∞)}。 注:C(3)表示C到起點D的距離是3。
第2步:將頂點C加入到S中。
上一步操作之后,U中頂點C到起點D的距離最短;因此,將C加入到S中,同時更新U中頂點的距離。以頂點F為例,之前F到D的距離為∞;但是將C加入到S之后,F到D的距離為9=(F,C)+(C,D)。
此時,S={D(0),C(3)}, U={A(∞),B(23),E(4),F(9),G(∞)}。
第3步:將頂點E加入到S中。
上一步操作之后,U中頂點E到起點D的距離最短;因此,將E加入到S中,同時更新U中頂點的距離。還是以頂點F為例,之前F到D的距離為9;但是將E加入到S之后,F到D的距離為6=(F,E)+(E,D)。
此時,S={D(0),C(3),E(4)}, U={A(∞),B(23),F(6),G(12)}。
第4步:將頂點F加入到S中。
此時,S={D(0),C(3),E(4),F(6)}, U={A(22),B(13),G(12)}。
第5步:將頂點G加入到S中。
此時,S={D(0),C(3),E(4),F(6),G(12)}, U={A(22),B(13)}。
第6步:將頂點B加入到S中。
此時,S={D(0),C(3),E(4),F(6),G(12),B(13)}, U={A(22)}。
第7步:將頂點A加入到S中。
此時,S={D(0),C(3),E(4),F(6),G(12),B(13),A(22)}。
此時,起點D到各個頂點的最短距離就計算出來了:A(22) B(13) C(3) D(0) E(4) F(6) G(12)。
三、狄傑斯特拉代碼說明
以"鄰接矩陣"為例對迪傑斯特拉算法進行說明,對於"鄰接表"實現的圖在后面會給出相應的源碼。
1. 基本定義
class MatrixUDG { #define MAX 100 #define INF (~(0x1<<31)) // 無窮大(即0X7FFFFFFF) private: char mVexs[MAX]; // 頂點集合 int mVexNum; // 頂點數 int mEdgNum; // 邊數 int mMatrix[MAX][MAX]; // 鄰接矩陣 public: // 創建圖(自己輸入數據) MatrixUDG(); // 創建圖(用已提供的矩陣) //MatrixUDG(char vexs[], int vlen, char edges[][2], int elen); MatrixUDG(char vexs[], int vlen, int matrix[][9]); ~MatrixUDG(); // 深度優先搜索遍歷圖 void DFS(); // 廣度優先搜索(類似於樹的層次遍歷) void BFS(); // prim最小生成樹(從start開始生成最小生成樹) void prim(int start); // 克魯斯卡爾(Kruskal)最小生成樹 void kruskal(); // Dijkstra最短路徑 void dijkstra(int vs, int vexs[], int dist[]); // 打印矩陣隊列圖 void print(); private: // 讀取一個輸入字符 char readChar(); // 返回ch在mMatrix矩陣中的位置 int getPosition(char ch); // 返回頂點v的第一個鄰接頂點的索引,失敗則返回-1 int firstVertex(int v); // 返回頂點v相對於w的下一個鄰接頂點的索引,失敗則返回-1 int nextVertex(int v, int w); // 深度優先搜索遍歷圖的遞歸實現 void DFS(int i, int *visited); // 獲取圖中的邊 EData* getEdges(); // 對邊按照權值大小進行排序(由小到大) void sortEdges(EData* edges, int elen); // 獲取i的終點 int getEnd(int vends[], int i); };
MatrixUDG是鄰接矩陣對應的結構體。
mVexs用於保存頂點,mVexNum是頂點數,mEdgNum是邊數;mMatrix則是用於保存矩陣信息的二維數組。例如,mMatrix[i][j]=1,則表示"頂點i(即mVexs[i])"和"頂點j(即mVexs[j])"是鄰接點;mMatrix[i][j]=0,則表示它們不是鄰接點。
2. 狄傑斯特拉算法
/* * Dijkstra最短路徑。 * 即,統計圖中"頂點vs"到其它各個頂點的最短路徑。 * * 參數說明: * vs -- 起始頂點(start vertex)。即計算"頂點vs"到其它頂點的最短路徑。 * prev -- 前驅頂點數組。即,prev[i]的值是"頂點vs"到"頂點i"的最短路徑所經歷的全部頂點中,位於"頂點i"之前的那個頂點。 * dist -- 長度數組。即,dist[i]是"頂點vs"到"頂點i"的最短路徑的長度。 */ void MatrixUDG::dijkstra(int vs, int prev[], int dist[]) { int i,j,k; int min; int tmp; int flag[MAX]; // flag[i]=1表示"頂點vs"到"頂點i"的最短路徑已成功獲取。 // 初始化 for (i = 0; i < mVexNum; i++) { flag[i] = 0; // 頂點i的最短路徑還沒獲取到。 prev[i] = 0; // 頂點i的前驅頂點為0。 dist[i] = mMatrix[vs][i]; // 頂點i的最短路徑為"頂點vs"到"頂點i"的權。 } // 對"頂點vs"自身進行初始化 flag[vs] = 1; dist[vs] = 0; // 遍歷mVexNum-1次;每次找出一個頂點的最短路徑。 for (i = 1; i < mVexNum; i++) { // 尋找當前最小的路徑; // 即,在未獲取最短路徑的頂點中,找到離vs最近的頂點(k)。 min = INF; for (j = 0; j < mVexNum; j++) { if (flag[j]==0 && dist[j]<min) { min = dist[j]; k = j; } } // 標記"頂點k"為已經獲取到最短路徑 flag[k] = 1; // 修正當前最短路徑和前驅頂點 // 即,當已經"頂點k的最短路徑"之后,更新"未獲取最短路徑的頂點的最短路徑和前驅頂點"。 for (j = 0; j < mVexNum; j++) { tmp = (mMatrix[k][j]==INF ? INF : (min + mMatrix[k][j])); if (flag[j] == 0 && (tmp < dist[j]) ) { dist[j] = tmp; prev[j] = k; } } } // 打印dijkstra最短路徑的結果 cout << "dijkstra(" << mVexs[vs] << "): " << endl; for (i = 0; i < mVexNum; i++) cout << " shortest(" << mVexs[vs] << ", " << mVexs[i] << ")=" << dist[i] << endl; }
完整代碼
1.鄰接矩陣源碼:

/** * C++: Dijkstra算法獲取最短路徑(鄰接矩陣) * * @author skywang * @date 2014/04/24 */ #include <iomanip> #include <iostream> #include <vector> using namespace std; // 邊的結構體 class EData { public: char start; // 邊的起點 char end; // 邊的終點 int weight; // 邊的權重 public: EData(){} EData(char s, char e, int w):start(s),end(e),weight(w){} }; class MatrixUDG { #define MAX 100 #define INF (~(0x1<<31)) // 無窮大(即0X7FFFFFFF) private: char mVexs[MAX]; // 頂點集合 int mVexNum; // 頂點數 int mEdgNum; // 邊數 int mMatrix[MAX][MAX]; // 鄰接矩陣 public: // 創建圖(自己輸入數據) MatrixUDG(); // 創建圖(用已提供的矩陣) //MatrixUDG(char vexs[], int vlen, char edges[][2], int elen); MatrixUDG(char vexs[], int vlen, int matrix[][9]); ~MatrixUDG(); // 深度優先搜索遍歷圖 void DFS(); // 廣度優先搜索(類似於樹的層次遍歷) void BFS(); // prim最小生成樹(從start開始生成最小生成樹) void prim(int start); // 克魯斯卡爾(Kruskal)最小生成樹 void kruskal(); // Dijkstra最短路徑 void dijkstra(int vs, int vexs[], int dist[]); // 打印矩陣隊列圖 void print(); private: // 讀取一個輸入字符 char readChar(); // 返回ch在mMatrix矩陣中的位置 int getPosition(char ch); // 返回頂點v的第一個鄰接頂點的索引,失敗則返回-1 int firstVertex(int v); // 返回頂點v相對於w的下一個鄰接頂點的索引,失敗則返回-1 int nextVertex(int v, int w); // 深度優先搜索遍歷圖的遞歸實現 void DFS(int i, int *visited); // 獲取圖中的邊 EData* getEdges(); // 對邊按照權值大小進行排序(由小到大) void sortEdges(EData* edges, int elen); // 獲取i的終點 int getEnd(int vends[], int i); }; /* * 創建圖(自己輸入數據) */ MatrixUDG::MatrixUDG() { char c1, c2; int i, j, weight, p1, p2; // 輸入"頂點數"和"邊數" cout << "input vertex number: "; cin >> mVexNum; cout << "input edge number: "; cin >> mEdgNum; if ( mVexNum < 1 || mEdgNum < 1 || (mEdgNum > (mVexNum * (mVexNum-1)))) { cout << "input error: invalid parameters!" << endl; return ; } // 初始化"頂點" for (i = 0; i < mVexNum; i++) { cout << "vertex(" << i << "): "; mVexs[i] = readChar(); } // 1. 初始化"邊"的權值 for (i = 0; i < mVexNum; i++) { for (j = 0; j < mVexNum; j++) { if (i==j) mMatrix[i][j] = 0; else mMatrix[i][j] = INF; } } // 2. 初始化"邊"的權值: 根據用戶的輸入進行初始化 for (i = 0; i < mEdgNum; i++) { // 讀取邊的起始頂點,結束頂點,權值 cout << "edge(" << i << "): "; c1 = readChar(); c2 = readChar(); cin >> weight; p1 = getPosition(c1); p2 = getPosition(c2); if (p1==-1 || p2==-1) { cout << "input error: invalid edge!" << endl; return ; } mMatrix[p1][p2] = weight; mMatrix[p2][p1] = weight; } } /* * 創建圖(用已提供的矩陣) * * 參數說明: * vexs -- 頂點數組 * vlen -- 頂點數組的長度 * matrix-- 矩陣(數據) */ MatrixUDG::MatrixUDG(char vexs[], int vlen, int matrix[][9]) { int i, j; // 初始化"頂點數"和"邊數" mVexNum = vlen; // 初始化"頂點" for (i = 0; i < mVexNum; i++) mVexs[i] = vexs[i]; // 初始化"邊" for (i = 0; i < mVexNum; i++) for (j = 0; j < mVexNum; j++) mMatrix[i][j] = matrix[i][j]; // 統計邊的數目 for (i = 0; i < mVexNum; i++) for (j = 0; j < mVexNum; j++) if (i!=j && mMatrix[i][j]!=INF) mEdgNum++; mEdgNum /= 2; } /* * 析構函數 */ MatrixUDG::~MatrixUDG() { } /* * 返回ch在mMatrix矩陣中的位置 */ int MatrixUDG::getPosition(char ch) { int i; for(i=0; i<mVexNum; i++) if(mVexs[i]==ch) return i; return -1; } /* * 讀取一個輸入字符 */ char MatrixUDG::readChar() { char ch; do { cin >> ch; } while(!((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z'))); return ch; } /* * 返回頂點v的第一個鄰接頂點的索引,失敗則返回-1 */ int MatrixUDG::firstVertex(int v) { int i; if (v<0 || v>(mVexNum-1)) return -1; for (i = 0; i < mVexNum; i++) if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF) return i; return -1; } /* * 返回頂點v相對於w的下一個鄰接頂點的索引,失敗則返回-1 */ int MatrixUDG::nextVertex(int v, int w) { int i; if (v<0 || v>(mVexNum-1) || w<0 || w>(mVexNum-1)) return -1; for (i = w + 1; i < mVexNum; i++) if (mMatrix[v][i]!=0 && mMatrix[v][i]!=INF) return i; return -1; } /* * 深度優先搜索遍歷圖的遞歸實現 */ void MatrixUDG::DFS(int i, int *visited) { int w; visited[i] = 1; cout << mVexs[i] << " "; // 遍歷該頂點的所有鄰接頂點。若是沒有訪問過,那么繼續往下走 for (w = firstVertex(i); w >= 0; w = nextVertex(i, w)) { if (!visited[w]) DFS(w, visited); } } /* * 深度優先搜索遍歷圖 */ void MatrixUDG::DFS() { int i; int visited[MAX]; // 頂點訪問標記 // 初始化所有頂點都沒有被訪問 for (i = 0; i < mVexNum; i++) visited[i] = 0; cout << "DFS: "; for (i = 0; i < mVexNum; i++) { //printf("\n== LOOP(%d)\n", i); if (!visited[i]) DFS(i, visited); } cout << endl; } /* * 廣度優先搜索(類似於樹的層次遍歷) */ void MatrixUDG::BFS() { int head = 0; int rear = 0; int queue[MAX]; // 輔組隊列 int visited[MAX]; // 頂點訪問標記 int i, j, k; for (i = 0; i < mVexNum; i++) visited[i] = 0; cout << "BFS: "; for (i = 0; i < mVexNum; i++) { if (!visited[i]) { visited[i] = 1; cout << mVexs[i] << " "; queue[rear++] = i; // 入隊列 } while (head != rear) { j = queue[head++]; // 出隊列 for (k = firstVertex(j); k >= 0; k = nextVertex(j, k)) //k是為訪問的鄰接頂點 { if (!visited[k]) { visited[k] = 1; cout << mVexs[k] << " "; queue[rear++] = k; } } } } cout << endl; } /* * 打印矩陣隊列圖 */ void MatrixUDG::print() { int i,j; cout << "Martix Graph:" << endl; for (i = 0; i < mVexNum; i++) { for (j = 0; j < mVexNum; j++) cout << setw(10) << mMatrix[i][j] << " "; cout << endl; } } /* * prim最小生成樹 * * 參數說明: * start -- 從圖中的第start個元素開始,生成最小樹 */ void MatrixUDG::prim(int start) { int min,i,j,k,m,n,sum; int index=0; // prim最小樹的索引,即prims數組的索引 char prims[MAX]; // prim最小樹的結果數組 int weights[MAX]; // 頂點間邊的權值 // prim最小生成樹中第一個數是"圖中第start個頂點",因為是從start開始的。 prims[index++] = mVexs[start]; // 初始化"頂點的權值數組", // 將每個頂點的權值初始化為"第start個頂點"到"該頂點"的權值。 for (i = 0; i < mVexNum; i++ ) weights[i] = mMatrix[start][i]; // 將第start個頂點的權值初始化為0。 // 可以理解為"第start個頂點到它自身的距離為0"。 weights[start] = 0; for (i = 0; i < mVexNum; i++) { // 由於從start開始的,因此不需要再對第start個頂點進行處理。 if(start == i) continue; j = 0; k = 0; min = INF; // 在未被加入到最小生成樹的頂點中,找出權值最小的頂點。 while (j < mVexNum) { // 若weights[j]=0,意味着"第j個節點已經被排序過"(或者說已經加入了最小生成樹中)。 if (weights[j] != 0 && weights[j] < min) { min = weights[j]; k = j; } j++; } // 經過上面的處理后,在未被加入到最小生成樹的頂點中,權值最小的頂點是第k個頂點。 // 將第k個頂點加入到最小生成樹的結果數組中 prims[index++] = mVexs[k]; // 將"第k個頂點的權值"標記為0,意味着第k個頂點已經排序過了(或者說已經加入了最小樹結果中)。 weights[k] = 0; // 當第k個頂點被加入到最小生成樹的結果數組中之后,更新其它頂點的權值。 for (j = 0 ; j < mVexNum; j++) { // 當第j個節點沒有被處理,並且需要更新時才被更新。 if (weights[j] != 0 && mMatrix[k][j] < weights[j]) weights[j] = mMatrix[k][j]; } } // 計算最小生成樹的權值 sum = 0; for (i = 1; i < index; i++) { min = INF; // 獲取prims[i]在mMatrix中的位置 n = getPosition(prims[i]); // 在vexs[0...i]中,找出到j的權值最小的頂點。 for (j = 0; j < i; j++) { m = getPosition(prims[j]); if (mMatrix[m][n]<min) min = mMatrix[m][n]; } sum += min; } // 打印最小生成樹 cout << "PRIM(" << mVexs[start] << ")=" << sum << ": "; for (i = 0; i < index; i++) cout << prims[i] << " "; cout << endl; } /* * 獲取圖中的邊 */ EData* MatrixUDG::getEdges() { int i,j; int index=0; EData *edges; edges = new EData[mEdgNum]; for (i=0; i < mVexNum; i++) { for (j=i+1; j < mVexNum; j++) { if (mMatrix[i][j]!=INF) { edges[index].start = mVexs[i]; edges[index].end = mVexs[j]; edges[index].weight = mMatrix[i][j]; index++; } } } return edges; } /* * 對邊按照權值大小進行排序(由小到大) */ void MatrixUDG::sortEdges(EData* edges, int elen) { int i,j; for (i=0; i<elen; i++) { for (j=i+1; j<elen; j++) { if (edges[i].weight > edges[j].weight) { // 交換"邊i"和"邊j" swap(edges[i], edges[j]); } } } } /* * 獲取i的終點 */ int MatrixUDG::getEnd(int vends[], int i) { while (vends[i] != 0) i = vends[i]; return i; } /* * 克魯斯卡爾(Kruskal)最小生成樹 */ void MatrixUDG::kruskal() { int i,m,n,p1,p2; int length; int index = 0; // rets數組的索引 int vends[MAX]={0}; // 用於保存"已有最小生成樹"中每個頂點在該最小樹中的終點。 EData rets[MAX]; // 結果數組,保存kruskal最小生成樹的邊 EData *edges; // 圖對應的所有邊 // 獲取"圖中所有的邊" edges = getEdges(); // 將邊按照"權"的大小進行排序(從小到大) sortEdges(edges, mEdgNum); for (i=0; i<mEdgNum; i++) { p1 = getPosition(edges[i].start); // 獲取第i條邊的"起點"的序號 p2 = getPosition(edges[i].end); // 獲取第i條邊的"終點"的序號 m = getEnd(vends, p1); // 獲取p1在"已有的最小生成樹"中的終點 n = getEnd(vends, p2); // 獲取p2在"已有的最小生成樹"中的終點 // 如果m!=n,意味着"邊i"與"已經添加到最小生成樹中的頂點"沒有形成環路 if (m != n) { vends[m] = n; // 設置m在"已有的最小生成樹"中的終點為n rets[index++] = edges[i]; // 保存結果 } } delete[] edges; // 統計並打印"kruskal最小生成樹"的信息 length = 0; for (i = 0; i < index; i++) length += rets[i].weight; cout << "Kruskal=" << length << ": "; for (i = 0; i < index; i++) cout << "(" << rets[i].start << "," << rets[i].end << ") "; cout << endl; } /* * Dijkstra最短路徑。 * 即,統計圖中"頂點vs"到其它各個頂點的最短路徑。 * * 參數說明: * vs -- 起始頂點(start vertex)。即計算"頂點vs"到其它頂點的最短路徑。 * prev -- 前驅頂點數組。即,prev[i]的值是"頂點vs"到"頂點i"的最短路徑所經歷的全部頂點中,位於"頂點i"之前的那個頂點。 * dist -- 長度數組。即,dist[i]是"頂點vs"到"頂點i"的最短路徑的長度。 */ void MatrixUDG::dijkstra(int vs, int prev[], int dist[]) { int i,j,k; int min; int tmp; int flag[MAX]; // flag[i]=1表示"頂點vs"到"頂點i"的最短路徑已成功獲取。 // 初始化 for (i = 0; i < mVexNum; i++) { flag[i] = 0; // 頂點i的最短路徑還沒獲取到。 prev[i] = 0; // 頂點i的前驅頂點為0。 dist[i] = mMatrix[vs][i]; // 頂點i的最短路徑為"頂點vs"到"頂點i"的權。 } // 對"頂點vs"自身進行初始化 flag[vs] = 1; dist[vs] = 0; // 遍歷mVexNum-1次;每次找出一個頂點的最短路徑。 for (i = 1; i < mVexNum; i++) { // 尋找當前最小的路徑; // 即,在未獲取最短路徑的頂點中,找到離vs最近的頂點(k)。 min = INF; for (j = 0; j < mVexNum; j++) { if (flag[j]==0 && dist[j]<min) { min = dist[j]; k = j; } } // 標記"頂點k"為已經獲取到最短路徑 flag[k] = 1; // 修正當前最短路徑和前驅頂點 // 即,當已經"頂點k的最短路徑"之后,更新"未獲取最短路徑的頂點的最短路徑和前驅頂點"。 for (j = 0; j < mVexNum; j++) { tmp = (mMatrix[k][j]==INF ? INF : (min + mMatrix[k][j])); if (flag[j] == 0 && (tmp < dist[j]) ) { dist[j] = tmp; prev[j] = k; } } } // 打印dijkstra最短路徑的結果 cout << "dijkstra(" << mVexs[vs] << "): " << endl; for (i = 0; i < mVexNum; i++) cout << " shortest(" << mVexs[vs] << ", " << mVexs[i] << ")=" << dist[i] << endl; } int main() { int prev[MAX] = {0}; int dist[MAX] = {0}; char vexs[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'}; int matrix[][9] = { /*A*//*B*//*C*//*D*//*E*//*F*//*G*/ /*A*/ { 0, 12, INF, INF, INF, 16, 14}, /*B*/ { 12, 0, 10, INF, INF, 7, INF}, /*C*/ { INF, 10, 0, 3, 5, 6, INF}, /*D*/ { INF, INF, 3, 0, 4, INF, INF}, /*E*/ { INF, INF, 5, 4, 0, 2, 8}, /*F*/ { 16, 7, 6, INF, 2, 0, 9}, /*G*/ { 14, INF, INF, INF, 8, 9, 0}}; int vlen = sizeof(vexs)/sizeof(vexs[0]); MatrixUDG* pG; // 自定義"圖"(輸入矩陣隊列) //pG = new MatrixUDG(); // 采用已有的"圖" pG = new MatrixUDG(vexs, vlen, matrix); //pG->print(); // 打印圖 //pG->DFS(); // 深度優先遍歷 //pG->BFS(); // 廣度優先遍歷 //pG->prim(0); // prim算法生成最小生成樹 //pG->kruskal(); // Kruskal算法生成最小生成樹 // dijkstra算法獲取"第4個頂點"到其它各個頂點的最短距離 pG->dijkstra(3, prev, dist); return 0; }
2.鄰接表源碼:
本文來自https://www.cnblogs.com/skywang12345/p/3711514.html