迪杰斯特拉(Dijkstra)算法
引言:我们常常纠结一个对路径选择的决策问题,假设我们要从北京到上海,那么如何才能走 花最少的钱,又最节省时间的线路呢?
这时候,我们可以把从 北京 到 上海 间的路线站标记,那么 北京 到 各路线站 都会有相应的金钱和时间花费,我们只需要找出一条从北京到上海所经过的路线站的时间和金钱总值消耗最少的即可。
显而易见,对应到图中,就是一张带权的图,即 一张 网。我们只需要找出 起点 到 终点 权值之和最少的路径即可。即
t
a
r
g
e
t
=
M
i
n
(
∑
b
e
g
i
n
e
n
d
w
e
i
g
h
t
)
i
target=Min{ (\sum_{begin}^{end}weight)_i }
target=Min(begin∑endweight)i
1.算法思想及其步骤
算 法 核 心 \color{SpringGreen}算法核心 算法核心:基于已经求得最短路径的基础上,求得更远顶点的最短路径
图结构 graphNet : 带权的图 (网),用邻接矩阵表示
路径前驱数组 path[ ] :存储 从起点 V0 到 终点 Vm 的 当前 最短路径 终点 的前驱 ,即 V0->Vm的前驱->Vm(此步在接下来会充分解析)
最短路径数组 dist[ ] :存储 从起点 V0 到 终点 Vm 的 当前 最短路径长度,即当前的权值之和
集合数组 S[ ] :检查 从起点 V0 到 某顶点 是否已经求得最短路径
集合 V : 图节点总数
Step1 | 将与V0有连线的顶点之间 对应的最短路径长度 path[ ] 初始化为 权值 |
---|---|
Step2 | 选择Vm,使dist[ Vm ]=Min{ dist[ Vi ], v i v_i vi∈V-S} |
Step3 | 将Vm加入S中 |
Step4 | 在求得 dist[ Vm ]的基础上,修正从起点 V0 到集合V-S 上任意顶点 Vi 的最短路径的长度 |
重复以上 2~4 步共 n-1 次即可求得 V0 到图中 其他每个顶点 对应的最短路径
接下来我们来对算法步骤进行剖析,下面是一张网 以及对应的邻接矩阵
图 (Graph) 邻接矩阵(Adjesent Matrix)
执行Step1初始化后,我们将会得到以下结果,由于从 V0开始,从 V0 到 V0 最短路径为零,故V0 率先加入到 集合 S[ ]中,标明已经求得最短路径。数组path[ ] 置为 -1,表示目前还没求得 V0 到 连线的顶点 的 路径前驱
执行Step2,找到距离 V0 最近的连线顶点 Vm,即 V1
将 V1 置入 集合 S[ ] 中,即 s[ V1]=1
相应地,我们可求得 V1 到 V2,V3,V4 路径长。在其基础上,更新修正 dist[ ] 中 V0 到 V2,V3,V4 的最短路径长,更新 path 中 相应的 当前 最短路径的前驱,例如 从 V0 到 V3 的当前最短路径前驱 是 V1,即 V0 -> V1 -> V3,故 path[ V3 ]=1
对标 Step2,我们每次都使用,上次所求得的节点中 距离 相对起点( 这里为 V1) 最近的节点作为下一个程序开始的相对起点 (这里为 V2),同样依次求 V2 到它有连线的顶点的距离(这里为 V4 和 V5 ),进而求得 V0 到 V4,V5 的对应的 新的最短路径长, 更新 path 中 相应的路径前驱以及集合S,此时我们会发现 path[ V4 ]=2,这是什么意思呢? 难道 是 从 V0 经过 V2 直接到达 V4 ,即( V0 -> V2 -> V4 )是当前最短路径吗?
其实我们可以这样 理解,要想知道 起点 V0 到 V2 的最短路径 ,势必会有 一个节点 作为 V2 的前驱,这里 即是 V1。那么同样地,要想 知道 V0 到 V4 的最短路径,也势必有一个节点 作为 V4 的前驱,只不过这时会有 一个顶点作为相对起点 (V1)。故此 上式 ( V0 -> V2 -> V4 )的完全体 应为 (V0 ->V1-> V2 -> V4)
同样,我们将 上一步求得 距离 相对起点 V2 最近的 V4 ,作为下一个相对起点求取 其 和有连线顶点的距离,并更新 距离数组 dist 和 前驱数组 path 中相应的值
重复 Step2 ~ Step4共 n-1 次,我们即可求得 起点 V0 到 终点 V8 的最短路径 :
2.代码
2.1 相关声明
#define GRAPH_INFINITY 65535 /* 无穷大 */
#define MAXVEX 20 /*最大图顶点*/
#define MAXEDGE 20 /*最大图边*/
typedef struct
{
int vexs[MAXVEX];/* 顶点 */
int arc[MAXVEX][MAXVEX];/* 邻接矩阵 */
int numVertexes;/* 顶点数 */
int numEdges;/* 边数 */
}graphNet;
typedef int Dist[MAXVEX];/*存储从起点V0到终点Vm的当前最短路径长度,即当前的权值之和 */
typedef int Path[MAXVEX];/*存储从起点V0到终点Vm的当前最短路径的前驱 */
2.2 有权图的建立函数定义
void creatGraphNet(graphNet *G)/* 构建图 */
{
int ii,jj;
/*输入 边数 和 顶点 数,这里 可用 printf()*/
G->numVertexes=9;
G->numEdges=16;
/*初始化 图*/
for(ii=0; ii<G->numsVertexes; i++)
{
G->vexs[i]=ii;
}
for( ii=0; i<G->numVertexes; ii++)
{
for(jj=0 ;jj<G->numVertexes; jj++)
{
if(ii==jj)
G->arc[ii][jj]=0;
else
G->arc[ii][jj]=G->arc[ii][jj]=GRAPH_INFINITY;
}
}
/*赋予 权值*/
G->arc[0][1] = 1;
G->arc[0][2] = 5;
G->arc[1][2] = 3;
G->arc[1][3] = 7;
G->arc[1][4] = 5;
G->arc[2][4] = 1;
G->arc[2][5] = 7;
G->arc[3][4] = 2;
G->arc[3][6] = 3;
G->arc[4][5] = 3;
G->arc[4][6] = 6;
G->arc[4][7] = 9;
G->arc[5][7] = 5;
G->arc[6][7] = 2;
G->arc[6][8] = 7;
G->arc[7][8] = 4;
for (ii = 0; ii < G->numVertexes; ii++)
{
for (jj = ii; jj < G->numVertexes; jj++)
{
G->arc[jj][ii] = G->arc[ii][jj];
}
}
}
2.3 核心算法:迪杰斯特拉
void ShortestPath_Dijkstra(graphNet G, int v0, Dist* D, Path* P)
{
int v, vm, k, min;
int S[MAXVEX];/* S[vm]=1表示求得顶点v0至vm的最短路径 */
for (v = 0; v < G.numVertexes; v++) /* 初始化数据 */
{
S[v] = 0; /* 全部顶点初始化为未知最短路径状态 */
(*D)[v] = G.arc[v0][v];/* 将与v0点有连线的顶点加上权值 */
(*P)[v] = -1; /* 初始化路径数组P为-1 */
}
(*D)[v0] = 0; /* v0至v0路径为0 */
S[v0] = 1; /* v0至v0求路径的最短路径 0 */
/* 开始主循环,每次求得v0到某个v顶点的最短路径 */
for (v = 1; v < G.numVertexes; v++)
{
min = GRAPH_INFINITY; /* 当前所知离v0顶点的最近距离 */
for (vm = 0; vm < G.numVertexes; vm++) /* 寻找离v0最近的顶点 */
{
if (!S[vm] && (*D)[vm] < min)
{
k = vm;
min = (*D)[vm]; /* m顶点离v0顶点更近 */
}
}
S[k] = 1; /* 将目前找到的最近的顶点置入 集合 S */
for (vm = 0; vm < G.numVertexes; vm++) /* 修正当前最短路径及距离 */
{
/* 如果经过v顶点的路径比现在这条路径的长度短的话 */
if (!S[vm] &&G.arc[k][vm]<GRAPH_INFINITY && (min + G.arc[k][vm] < (*D)[vm]))
{
/* 说明找到了更短的路径,修改D[vm]和P[vm] */
(*D)[vm] = min + G.arc[k][vm]; /* 修改当前路径长度 */
(*P)[vm] = k;
}
}
}
}
此算法讲解到这里就结束了,如有错误与不足之处,欢迎指出,大家共同进步!!