最短路径—Dijkstra算法和Floyd算法
1、单源点的最短路径问题:给定带权有向图G和源点v,求从v到G中其余各顶点的最短路径。
我们用一个例子来具体说明迪杰斯特拉算法的流程。
定义源点为 0,dist[i]
为源点 0 到顶点 i 的最短路径。其过程描述如下:
步骤 | dist[1] | dist[2] | dist[3] | dist[4] | 已找到的集合 |
---|---|---|---|---|---|
第 1 步 | 8 | 1 | 2 | +∞ | {2} |
第 2 步 | 8 | × | 2 | 4 | {2, 3} |
第 3 步 | 5 | × | × | 4 | {2, 3, 4} |
第 4 步 | 5 | × | × | × | {2, 3, 4, 1} |
第 5 步 | × | × | × | × | {2, 3, 4, 1} |
第 1 步:从源点 0 开始,找到与其邻接的点:1,2,3,更新dist[]
数组,因 0 不与 4 邻接,故dist[4]
为正无穷。在dist[]
中找到最小值,其顶点为 2,即此时已找到 0 到 2 的最短路。
第 2 步:从 2 开始,继续更新dist[]
数组:2 与 1 不邻接,不更新;2 与 3 邻接,因0→2→3
比dist[3]
大,故不更新dist[3]
;2 与 4 邻接,因0→2→4
比dist[4]
小,故更新dist[4]
为 4。在dist[]
中找到最小值,其顶点为 3,即此时又找到 0 到 3 的最短路。
第 3 步:从 3 开始,继续更新dist[]
数组:3 与 1 邻接,因0→3→1
比dist[1]
小,更新dist[1]
为 5;3 与 4 邻接,因0→3→4
比dist[4]
大,故不更新。在dist[]
中找到最小值,其顶点为 4,即此时又找到 0 到 4 的最短路。
第 4 步:从 4 开始,继续更新dist[]
数组:4 与 1 不邻接,不更新。在dist[]
中找到最小值,其顶点为 1,即此时又找到 0 到 1 的最短路。
第 5 步:所有点都已找到,停止。
对于上述步骤,你可能存在以下的疑问:
若 A 作为源点,与其邻接的只有 B,C,D 三点,其dist[]
最小时顶点为 C,即就可以确定A→C
为 A 到 C 的最短路。但是我们存在疑问的是:是否还存在另一条路径使 A 到 C 的距离更小? 用反证法证明。
假设存在如上图的红色虚线路径,使A→D→C
的距离更小,那么A→D
作为A→D→C
的子路径,其距离也比A→C
小,这与前面所述 “dist[]
最小时顶点为 C” 矛盾,故假设不成立。因此这个疑问不存在。
根据上面的证明,我们可以推断出,Dijkstra 每次循环都可以确定一个顶点的最短路径,故程序需要循环 n-1 次。
1 /* 2 迪杰斯特拉求单节点到其余各节点的最短路径。 3 visited数组用于保存顶点是否已经求过最短路径,pre数组用于保存最短路径的下标 4 dist数组用于保存初始节点到其余节点的最短路径长度。 5 该算法求有向图G的某顶点到其余节点的最短路径pre以及长度dist 6 pre[v]的值是v0-->...->v的路径中的前驱节点。D[v]表示v0-->...-->v的最短路径长度和。 7 8 可以证明迪杰斯特拉算法每次循环可以确定一个顶点的最短路径,所以主程序循环n-1次。 9 主程序循环主要做两件事:首先找出dist数组中的最小值,并记录下标,说明找到初始点到该下标的最短路径。 10 然后要比价初始点到该点的最短路径加上这点到其他初始点需要到的点的距离是否比初始点直接到这些点的距离短 11 如果要短,那么就更新dist数组,并且这些点的前驱节点就会变为v而不是开始的v0点。下一次主循环再去从dist中找 12 最小的值并且未求过的点,就是该点的最短路径。 13 */ 14 #include<iostream> 15 using namespace std; 16 int matrix[100][100];//邻接矩阵 17 bool visited[100];//标记数组 18 int dist[100];//原点到i顶点的最短距离 19 int pre[100];//记录最短路径。pre[i]放的是i的前驱节点 20 int source;//源节点 21 int vertex_num;//顶点数 22 int edge_num;//边数 23 24 void Dijkstra(int source) 25 { 26 //首先初始化 27 memset(visited,0,sizeof(visited)); 28 visited[source] = true; 29 for (int i = 0; i < vertex_num; i++) 30 { 31 dist[i] = matrix[source][i]; 32 pre[i] = source; 33 } 34 35 int min_cost;//最短距离 36 int min_cost_index;//权值最小的那个顶点的下标。(求好了) 37 //主循环 38 for (int i = 1; i < vertex_num; i++) 39 { 40 min_cost = INT_MAX; 41 for (int j = 0; j < vertex_num; j++) 42 { 43 //注意要确保这个点没有找过。 44 if (visited[j]==false&&dist[j] < min_cost) 45 { 46 min_cost_index = j; 47 min_cost = dist[j]; 48 } 49 } 50 51 visited[min_cost_index] = true;//找到某一个点的最短距离 52 //利用该点进行dist的更新,并且调整前驱。 53 for (int j = 0; j < vertex_num; j++) 54 { 55 //确保有连接 56 if (visited[j] == false && matrix[min_cost_index][j] != INT_MAX&&min_cost+ matrix[min_cost_index][j] < dist[j]) 57 { 58 dist[j] = min_cost + matrix[min_cost_index][j]; 59 pre[j] = min_cost_index; 60 } 61 } 62 } 63 } 64 65 int main() 66 { 67 cout << "请输入图的顶点数(<100):"; 68 cin >> vertex_num; 69 cout << "请输出图的边数: "; 70 cin >> edge_num; 71 for (int i = 0; i < vertex_num; i++) 72 { 73 for (int j = 0; j < vertex_num; j++) 74 { 75 matrix[i][j] = (i != j) ? INT_MAX : 0; 76 } 77 } 78 cout << "请输入边的信息:\n"; 79 int u, v, w; 80 for (int i = 0; i < edge_num; i++) 81 { 82 cin >> u >> v >> w; 83 matrix[u][v] = matrix[v][u] = w; 84 } 85 86 cout << "请输入源点(<" << vertex_num << "): "; 87 cin >> source; 88 Dijkstra(source); 89 for (int i = 0; i < vertex_num; i++) 90 { 91 if (i != source) 92 { 93 //路径是反的,从目标点向前不断找前驱的过程。 94 cout << source << "到" << i << "最短距离: " << dist[i] << ",路径是:" << i; 95 int t = pre[i]; 96 while (t != source) 97 { 98 cout << "--" << t; 99 t = pre[t]; 100 } 101 cout << "--" << source << endl; 102 } 103 } 104 return 0; 105 }
2、弗洛伊德算法:
1)算法思想原理:
Floyd算法是一个经典的动态规划算法。用通俗的语言来描述的话,首先我们的目标是寻找从点i到点j的最短路径。从动态规划的角度看问题,我们需要为这个目标重新做一个诠释(这个诠释正是动态规划最富创造力的精华所在)
从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,我们假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
2).算法描述:
a.从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
b.对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。
3).Floyd算法过程矩阵的计算----十字交叉法
方法:两条线,从左上角开始计算一直到右下角 如下所示
给出矩阵,其中矩阵A是邻接矩阵,而矩阵Path记录u,v两点之间最短路径所必须经过的点
相应计算方法如下:
最后A3即为所求结果
1 typedef struct 2 { 3 char vertex[VertexNum]; //顶点表 4 int edges[VertexNum][VertexNum]; //邻接矩阵,可看做边表 5 int n,e; //图中当前的顶点数和边数 6 }MGraph; 7 8 void Floyd(MGraph g) 9 { 10 int A[MAXV][MAXV]; 11 int path[MAXV][MAXV]; 12 int i,j,k,n=g.n; 13 for(i=0;i<n;i++) 14 for(j=0;j<n;j++) 15 { 16 A[i][j]=g.edges[i][j]; 17 path[i][j]=-1; 18 } 19 for(k=0;k<n;k++) 20 { 21 for(i=0;i<n;i++) 22 for(j=0;j<n;j++) 23 if(A[i][j]>(A[i][k]+A[k][j])) 24 { 25 A[i][j]=A[i][k]+A[k][j]; 26 path[i][j]=k; 27 } 28 } 29 }