一、图的最短路径
在现实生活中, 我们经常会面临一个路径选择的问题, 例如: 乘坐地铁, 在整个地铁线路中以最快的方式达到自己需要到达的站点.
整个地铁图就可以看作是一个网图, 来求A站到B站效率最高的线路. 对于网图来说, 最短路径是指两个顶点之间经过的边上权值只和最少的路径. 并且称路径上第一个顶点为源点, 最后一个顶点为终点.

如图需要找到V0到V8到最小路径. 这里探索两个算法 -- 迪杰斯特拉(Djkstra)算法、弗洛伊德(Floyd)算法
二、迪杰斯特拉(Djkstra)算法
这是一个按路径长度递增的次序产生最短路径的算法.
- 从V0开始, 找最小权值短边, 就是连接顶点V1,
- 从V1开始, 找最小权值短边, 就是连接顶点V2
- 从V2开始, 找最小权值短边, 就是连接顶点V4
依此类推找到V8, 此时得到的是局部最优路径.
在这个算法里需要用到三个数组: final数组、D数组、P数组
- final数组: 表示V0到某个顶点Vw是否已经求得里最短路径的标记. 如果V0到Vw已经有结果, 则finale[w] = 1
- D数组: 表示V0到某个顶点Vw的路径
- P数组: 当前顶点的前驱顶点的下标
例如:

- 当前顶点为V0, V0到V0没有路径即为0, 所以final[0] = 1
- 当前顶点为V0, V0到V1 V2分别为1 5, 所以D[1] = 1, D[2] = 5,
- 当前顶点为V0, V0没有前驱, 为P[0] = -1
- 比较D数组, 得到最小到路径为1, 即V0 V1为最小路径, 所以选择顶点V1
再次进行处理:

- 当前顶点为V1, V1到V2为3, 已知V0到V1为1, 所以V0到V2可以为4, D[2] = 5 > 4, 更新D[2] = 4
- V1到V3为7, 已知V0到V1为1, 所以V0到V3可以为8, 更新D[3] = 8
- V1到V4为5, 已知V0到V1为1, 所以V0到V4可以为6, 更新D[4] = 6
- 当前顶点为V1,V2 V3 V4上一个顶点为V1, 记录P[2] = 1, P[3] = 1, P[4] = 1
依次类推.
2.1、Djkstra算法代码实现
/*用于存储最短路径下标的数组*/
typedef int Patharc[MAXVEX];
/*用于存储到各点最短路径权值的和*/
typedef int ShortPathTable[MAXVEX];
void ShortestPath_Dijkstra(MGraph G, int v0, Patharc *P, ShortPathTable *D) {
int final[MAXVEX];
/*初始化数据*/
for(int v = 0; v < G.numVertexes; v++)
{
//全部顶点初始化为未知最短路径状态0
final[v] = 0;
//将与V0 点有连线的顶点最短路径值;
(*D)[v] = G.arc[v0][v];
//初始化路径数组p = 0;
(*P)[v] = 0;
}
final[v0] = 1;
(*P)[v0] = -1;
//找V0到某个顶点最短路径
int k = 0;
for (int i = 0; i < G.numVertexes; i++) {
int min = INFINITYC;
//找到当前最小的边
for (int j = 0; j < G.numVertexes; j++) {
if (!final[j] && (*D)[j] < min) {
min = (*D)[j];
k = j;
}
}
final[k] = 1;
for (int j = 0; j < G.numVertexes; j++) {
//如果经过v顶点的路径比现在这条路径长度短,则更新
if (!final[j] && (min + G.arc[k][j] < (*D)[j])) {
//修改当前路径的长度
(*D)[j] = min + G.arc[k][j];
(*P)[j]=k;
}
}
}
//打印最短路径
printf("最短路径路线:\n");
int j = 0;
for(int i = 1; i < G.numVertexes;++i)
{
printf("v%d -> v%d : V%d ", v0, i, i);
j=i;
while((*P)[j] != -1)
{
printf("V%d ", (*P)[j]);
j = (*P)[j];
}
printf("\n");
}
}
三、弗洛伊德(Floyd)算法
首先看一下例子:

定义两个二维数组D[3][3] P[3][3]. 代表顶点到顶点到最短路径权值和的矩阵.
代表对应顶点的最小路径的前驱矩阵.在处理前将
命名为
,
命名为
.
由图可知, V1到V2距离为5, 但是V1->V0->V2距离为3, 所以V0->V2和V2->V0可以修改为3. 按照这样来对进行处理得到
此时V2的前驱节点为V0, 所以修改的对应位置得到
所以根绝这种规律就有来一个公式:
接下来其实就是在的基础上继续处理所有顶点经过v1和v2后到达另一顶点的最短路径, 得到
直到所有的顶点都处理完毕.
以此图为例子创建,

- 首先初始化得到两个二维数组
,
- 当k = 1, v = 0, w=[0~8], 根据公式:
- D[0][0] = 0, D[0][1] + D[1][0] = 0, 不更新
- D[0][1] = 1, D[0][1] + D[1][1] = 1, 不更新
- D[0][2] = 5, D[0][1] + D[1][2] = 1 + 3 = 4, 4<5, 所以D[0][2] = 4
- D[0][3] = ∞, D[0][1] + D[1][3] = 1 + 7 = 8, 8<∞, 所以D[0][3] = 8
- D[0][4] = ∞, D[0][1] + D[1][4] = 1 + 5 = 6, 6<∞, 所以D[0][4] = 6
- D[0][5] = ∞, D[0][1] + D[1][5] = 1 + ∞, 不更新
- D[0][6] = ∞, D[0][1] + D[1][6] = 1 + ∞, 不更新
- D[0][7] = ∞, D[0][1] + D[1][7] = 1 + ∞, 不更新
- D[0][8] = ∞, D[0][1] + D[1][8] = 1 + ∞, 不更新
- 当k = 1, v = 1, w=[0~8], 根据公式:
- D[1][0] = 0, D[1][1] + D[1][0] = 1, 不更新
- D[1][1] = 1, D[1][1] + D[1][1] = 0, 不更新
- D[1][2] = 5, D[1][1] + D[1][2] = 0 + 3 = 3, 不更新
- D[1][3] = ∞, D[1][1] + D[1][3] = 0 + 7 = 7, 不更新
- D[1][4] = ∞, D[1][1] + D[1][4] = 0 + 5 = 5, 不更新
- D[1][5] = ∞, D[1][1] + D[1][5] = 0 + ∞, 不更新
- D[1][6] = ∞, D[1][1] + D[1][6] = 0 + ∞, 不更新
- D[1][7] = ∞, D[1][1] + D[1][7] = 0 + ∞, 不更新
- D[1][8] = ∞, D[1][1] + D[1][8] = 0 + ∞, 不更新
依次进行得到最终的数组为:

由图就可得知V0到V8最短距离为16.
路线为取P[0][8] = 1, P[1][8] = 2, P[2][8] = 4, P[4][8] = 3, P[3][8] = 6.....
得到最终路线为: V0 V1 V2 V4 V3 V6 V7 V8
3.1、Floyd算法代码实现
typedef int Patharc_Floyd[MAXVEX][MAXVEX];
typedef int ShortPathTable_Floyd[MAXVEX][MAXVEX];
void ShortestPath_Floyd(MGraph G, Patharc_Floyd *P, ShortPathTable_Floyd *D) {
//初始化D P数组
for (int v = 0; v < G.numVertexes; v++) {
for (int w = 0; w < G.numVertexes; w++) {
(*D)[v][w] = G.arc[v][w];
(*P)[v][w] = w;
}
}
for (int k = 0; k < G.numVertexes; k++) {
for (int v = 0; v < G.numVertexes; v++) {
for (int w = 0; w < G.numVertexes; w++) {
//如果经过下标为k顶点路径比原两点间路径更短
if ((*D)[v][w] > (*D)[v][k] + (*D)[k][w]) {
//将当前两点间权值设为更小的一个
(*D)[v][w]=(*D)[v][k]+(*D)[k][w];
//路径设置为经过下标为k的顶点
(*P)[v][w]=(*P)[v][k];
}
}
}
}
}