演算法導論 所有節點對的最短路徑
阿新 • • 發佈:2019-01-10
本章主要講述:
1.Floyd-Warshall演算法:求解任意兩點間的最短距離,時間複雜度為O(n^3)。
(1)使用條件&範圍:通常可以在任何圖中使用,包括有向圖、帶負權邊的圖。
(2)弗洛伊德(Floyd)演算法過程:
1、用D[v][w]記錄每一對頂點的最短距離。
2、依次掃描每一個點,並以其為基點再遍歷所有每一對頂點D[][]的值,看看是否 可用過該基點讓這對頂點間的距離更小。
(3)程式碼實現如下:
執行結果如下:#include <iostream> #include <string> #include <stdio.h> using namespace std; #define MaxVertexNum 100 #define INF 99999 /*** Floyd */ typedef struct { char vertex[MaxVertexNum]; int edges[MaxVertexNum][MaxVertexNum]; int n, e; }MGraph; void CreateMGraph(MGraph &G) { int i, j, k, p; cout << "請輸入頂點數和邊數:\n"; cin >> G.n >> G.e; cout << "請輸入頂點元素:\n"; for (i = 0; i<G.n; i++) { cin >> G.vertex[i]; } for (i = 0; i<G.n; i++) { for (j = 0; j<G.n; j++) { G.edges[i][j] = INF; if (i == j) { G.edges[i][j] = 0; } } } for (k = 0; k<G.e; k++) { cout << "請輸入第" << k + 1 << "條弧頭弧尾序號和相應的權值:\n"; cin >> i >> j >> p; G.edges[i][j] = p; } } void Dispath(int A[][MaxVertexNum], int path[][MaxVertexNum], int n); void Floyd(MGraph G) { int A[MaxVertexNum][MaxVertexNum], path[MaxVertexNum][MaxVertexNum]; int i, j, k; for (i = 0; i<G.n; i++) { for (j = 0; j<G.n; j++) { A[i][j] = G.edges[i][j]; path[i][j] = -1; } } for (k = 0; k<G.n; k++) { for (i = 0; i<G.n; i++) { for (j = 0; j<G.n; j++) { if (A[i][j]>A[i][k] + A[k][j]) { A[i][j] = A[i][k] + A[k][j]; path[i][j] = k; } } } } Dispath(A, path, G.n); } void Ppath(int path[][MaxVertexNum], int i, int j) { int k; k = path[i][j]; if (k == -1) { return; } Ppath(path, i, k); printf("%d,", k); Ppath(path, k, j); } void Dispath(int A[][MaxVertexNum], int path[][MaxVertexNum], int n) { int i, j; for (i = 0; i<n; i++) { for (j = 0; j<n; j++) { if (A[i][j] == INF) { if (i != j) { printf("從%d到%d沒有路徑\n", i, j); } } else { printf(" 從%d到%d=>路徑長度:%d路徑:\n", i, j, A[i][j]); printf("%d,", i); Ppath(path, i, j); printf("%d\n", j); } } } } int main() { freopen("input.txt", "r", stdin); MGraph G; CreateMGraph(G); Floyd(G); return 0; } /** floyd演算法輸入如下: 4 8 A B C D 0 1 6 0 3 3 1 0 5 1 2 1 2 0 3 2 3 2 3 0 8 3 1 2 */
2.Johnson演算法(稀疏圖):在O(V*V lgV + VE)的時間內找到所有節點對之間的最短路徑
(1)使用條件&範圍:通常可以在任何圖中使用,包括有向圖、帶負權邊的圖。
(2)Johnson演算法過程:
演算法中運用Dijskra、BellmanFord演算法,使用的技術是重新賦予權重,
a.如果圖G = (V, E)中權值全為非負值,則通過對所有結點執行一次dijkstra演算法找出所有結點對的最短路徑,
b.如果有非負值,但沒有權重為負值的環路,那麼只要計算出一組新的非負權重值,然後再用相同的方法即可。
(3)程式碼實現如下:
/************************************************************ Johnson.h: Johnson演算法,儲存為鄰接表, ************************************************************/ #include <vector> #include <queue> #include <stack> #include <iostream> #include <algorithm> #include <functional> using namespace std; //鄰接表的結構 struct ArcNode //表結點 { int source; //圖中該弧的源節點 int adjvex; //該弧所指向的頂點的位置 ArcNode *nextarc; //指向下一條弧的指標 int weight; //每條邊的權重 }; template <typename VertexType> struct VertexNode //頭結點 { VertexType data; //頂點資訊 ArcNode *firstarc; //指向第一條依附於該頂點的弧的指標 int key; //Prim:儲存連線該頂點和樹中結點的所有邊中最小邊的權重; //BellmanFord:記錄從源結點到該結點的最短路徑權重的上界 VertexNode *p; //指向在樹中的父節點 int indegree; //記錄每個頂點的入度 }; const int SIZE = 6; //圖的操作 template <typename VertexType> class ALGraph { public: typedef VertexNode<VertexType> VNode; ALGraph(int verNum) : vexnum(verNum), arcnum(0) { for (int i = 0; i < MAX_VERTEX_NUM; i++) { vertices[i].firstarc = NULL; vertices[i].key = INT_MAX / 2; vertices[i].p = NULL; vertices[i].indegree = 0; } } //構造演算法導論410頁圖(帶權有向圖) void createWDG() { cout << "構造演算法導論410頁圖(帶權有向圖)..." << endl; int i; for (i = 1; i < vexnum; i++) vertices[i].data = 'a' + i - 1; insertArc(1, 2, 3); insertArc(1, 3, 8); insertArc(1, 5, -4); insertArc(2, 4, 1); insertArc(2, 5, 7); insertArc(3, 2, 4); insertArc(4, 3, -5); insertArc(4, 1, 2); insertArc(5, 4, 6); } void createG() { cout << "構造圖G'...." << endl; vertices[0].data = 's'; insertArc(0, 1, 0); insertArc(0, 2, 0); insertArc(0, 3, 0); insertArc(0, 4, 0); insertArc(0, 5, 0); } //Johnson演算法,先使用BellmanFord演算法,使所有的邊的權重變為非負值, //然後運用dijkstra演算法求出結點對的最短路徑 int **Johnson() { createG(); //構造G’ displayGraph(); if (!BellmanFord(1)) cout << "the input graph contains a negative-weight cycle" << endl; else { int h[SIZE]; int i, j, k; //將陣列h[]的值設為執行BellmanFord後取得的值,h[i]為結點s到其他點的最短路徑 for (i = 0; i < vexnum; i++) h[i] = vertices[i].key; //遍歷所有的邊,將邊的權值重新賦值,即將所有的邊的權值改為負值 for (i = 0; i < vexnum; i++) { ArcNode *arc = vertices[i].firstarc; for (; arc != NULL; arc = arc->nextarc) arc->weight = arc->weight + h[arc->source] - h[arc->adjvex]; } //以下為程式碼: cout << "改變權重後的圖為:" << endl; displayGraph(); int **d = new int *[SIZE]; for (j = 0; j < SIZE; j++) d[j] = new int[SIZE]; //對每個結點執行dijkstra演算法,求出每個點到其他點的最短路徑,儲存在key中 for (k = 1; k < SIZE; k++) { Dijkstra(k + 1); for (i = 1; i < SIZE; i++) d[k][i] = vertices[i].key + h[i] - h[k]; } cout << "最後計算出的結點對的最短距離:" << endl; displayTwoDimArray(d); return d; } } //輸出一個二維陣列 void displayTwoDimArray(int **p) { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) cout << p[i][j] << " "; cout << endl; } cout << "~~~~~~~~~~~~~~~" << endl; } //列印鄰接連結串列 virtual void displayGraph() { for (int i = 0; i < vexnum; i++) { cout << "第" << i + 1 << "個頂點是:" << vertices[i].data << " 頂點的入度為:" << vertices[i].indegree << " 鄰接表為: "; ArcNode *arcNode = vertices[i].firstarc; while (arcNode != NULL) { cout << " -> " << vertices[arcNode->adjvex].data << "(" << arcNode->weight << ")"; arcNode = arcNode->nextarc; } cout << endl; } cout << "*******************************************************" << endl; } //PVnode排序準則 class PVNodeCompare { public: bool operator() (VNode *pvnode1, VNode *pvnode2) { return pvnode1->key > pvnode2->key; } }; //對每個結點的最短路徑估計和前驅結點進行初始化,最短路徑初始化為INT_MAX, p初始化為NULL //並將源節點的key初始化為0 void InitalizeSingleSource(int index) { for (int i = 0; i < MAX_VERTEX_NUM; i++) { vertices[i].key = INT_MAX >> 2; vertices[i].p = NULL; } vertices[index].key = 0; } //對邊(u, v)進行鬆弛,將目前s到v的最短路徑v.key與s到u的最短路徑加上w(u, v)的值進行比較 //如果比後面的值還大,則進行更新,將v.key縮短,並且將p置為u void relax(ArcNode *arc) { //竟然溢位了!! if (vertices[arc->adjvex].key > vertices[arc->source].key + arc->weight) { vertices[arc->adjvex].key = vertices[arc->source].key + arc->weight; vertices[arc->adjvex].p = &vertices[arc->source]; } } //BellmanFord, index為實際第幾個點 bool BellmanFord(int index) { InitalizeSingleSource(index - 1); for (int i = 1; i < vexnum; i++) //迴圈共進行vexnum-1次 { //遍歷所有的邊,並對每個邊進行一次鬆弛 for (int j = 0; j < vexnum; j++) { for (ArcNode *arc = vertices[j].firstarc; arc != NULL; arc = arc->nextarc) relax(arc); } } //再次遍歷所有的邊,檢查圖中是否存在權重為負值的環路,如果存在,則返回false for (int j = 0; j < vexnum; j++) { for (ArcNode *arc = vertices[0].firstarc; arc != NULL; arc = arc->nextarc) { if (vertices[arc->adjvex].key > vertices[arc->source].key + arc->weight) return false; } } cout << "BellmanFord求出的單源最短路徑:" << endl; for (int i = 1; i < vexnum; i++) { printPath(index - 1, i); } cout << "**************************************************" << endl; return true; } void Dijkstra(int index) { InitalizeSingleSource(index - 1); vector<VNode> snode; //儲存已經找到最短路徑的結點 vector<VNode *> que; //儲存結點的指標的陣列,用這個陣列執行堆的演算法 //將結點指標進佇列,形成以key為關鍵值的最小堆 for (int i = 0; i < vexnum; i++) que.push_back(&(vertices[i])); //使que按照pvnodecompare準則構成一個最小堆 make_heap(que.begin(), que.end(), PVNodeCompare()); while (que.empty() == false) { //將佇列中擁有最小key的結點出隊 VNode *node = que.front(); pop_heap(que.begin(), que.end(), PVNodeCompare()); //從堆中刪除最小的結點,只是放到了vector的最後 que.pop_back(); //將vector中的這個結點徹底刪除,因為後面還要再排序一次,以免影響後面的堆排序,pop演算法。 snode.push_back(*node); for (ArcNode *arc = node->firstarc; arc != NULL; arc = arc->nextarc) relax(arc); make_heap(que.begin(), que.end(), PVNodeCompare()); } cout << "Dijkstra求出的單源最短路徑:" << endl; for (int i = 1; i < vexnum; i++) { if (i != index - 1) printPath(index - 1, i); } cout << "**************************************************" << endl; } protected: //插入一個表結點 void insertArc(int vHead, int vTail, int weight) { //構造一個表結點 ArcNode *newArcNode = new ArcNode; newArcNode->source = vHead; newArcNode->adjvex = vTail; newArcNode->nextarc = NULL; newArcNode->weight = weight; //arcNode 是vertics[vHead]的鄰接表 ArcNode *arcNode = vertices[vHead].firstarc; if (arcNode == NULL) vertices[vHead].firstarc = newArcNode; else { while (arcNode->nextarc != NULL) { arcNode = arcNode->nextarc; } arcNode->nextarc = newArcNode; } arcnum++; vertices[vTail].indegree++; //對弧的尾結點的入度加1 } //列印源節點到i的最短路徑 void printPath(int i, int j) { cout << "從源節點 " << vertices[i].data << " 到目的結點 " << vertices[j].data << " 的最短路徑是:" /*<< endl*/; __printPath(&vertices[i], &vertices[j]); cout << " 權重為:" << vertices[j].key << endl; } void __printPath(VNode* source, VNode* dest) { if (source == dest) cout << source->data << "->"; else if (dest->p == NULL) cout << " no path!" << endl; else { __printPath(source, dest->p); cout << dest->data << "->"; } } private: //const資料成員必須在建構函式裡初始化 static const int MAX_VERTEX_NUM = 20; //最大頂點個數 VNode vertices[MAX_VERTEX_NUM]; //存放結點的陣列 int vexnum; //圖的當前頂點數 int arcnum; //圖的弧數 };
int main()
{
ALGraph<char> wdgGraph(6);
wdgGraph.createWDG();
wdgGraph.Johnson();
/*wdgGraph.displayGraph();*/
system("pause");
return 0;
}
執行結果如下: