一篇文章让你搞懂Dijikstra(迪杰斯特拉)算法
Posted 文坛小码农
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一篇文章让你搞懂Dijikstra(迪杰斯特拉)算法相关的知识,希望对你有一定的参考价值。
Dijikstra算法
算法解决的问题:求已知顶点V0到其他顶点的最短路径
图中任意两点间路径可以表示为
Pxy = pxa + pab + pbc +· L + pcy (全部的p>0)
若求<x,y>的最短路径,就是求 pxa,pab,pbc, pcy的最小值
而若使pab最小,前提是必须确定一个a,a又由pxa确认。
所以,迪杰斯特拉算法本质上是一个递推迭代的过程,知道了前一个才能知道后一个
定理一:与目标节点直接相连的多个节点中,权值最小的的节点与目标节点形成的路径是最短路径
证明:
先证充分性,即如果权值最小,那么是最短路径
如图所示,已知与v0直接相连的4个节点中,v1与v0直接相连且相较于其他直接相连的节点中路径最小。所有v1与v0间的最短距离确认
设P表示最短路径 P01表示从0到1的路径可表示为
P01 = P0a + Pab + Pbc +· L + Pc1 (全部的p>0)
对于不同的路径来说其少的只是 这个式子的中间项
设直接相连的P01 ‘= p01
非直接相连的 P01‘‘ = p0a + pa1;(a为中间节点)
又因为已知p01为所有直接相连节点中的最小值
所以有 p01 < p0a
有因为p>0
所以P01‘ < P01’’恒成立。
证得其充分性
下面来证明其必要性
已知最短路径,所以有 Po1 = p0a + pa1 最小
设数列Pn = p1 + p2 + p3 + p4 + …pn
Pn – Pn-1 = pn >0 恒成立,所以Pn为增数列
因此PnMin = p1 = p01
所以最小得路径一定是直接相连的最小值
证得其必要性
算法的执行思路
从前面的的递推式子讲起
同时也有
代入得
说明y到x的最短路径 等于 x到c的最短路径 + c到y的最短路径
因此,我们需要对每个节点都进行一个标记,来说明它是否已经找到最短路径
还需要一个数组来存储这个节点到我要找的目标节点的路径长度
当所有节点都确定找到最短路径后,这个长度数组就是我所要找的最短路经
Path数组的引入
前面提到过,任意两个节点的路径可以被描述为
而这个中间节点c是不确定的,我们求最短路径的目的就是为了找到这个c;这个中间节点c同目标节点x,y一样重要,是标识这个路径的唯一手段。
因此,我们需要引入一个path数组,来表明我的这条路径中间经过的节点数。
这样就可以说明x-y得最短路径为 x -> a -> b -> c -> y
所以path数组的含义是,y节点在找到x节点的最短路径中,最后一个经过的其他节点
辅助向量的代码展示
typedef struct Dijkstra
{
bool final[vertex_MAX]; //用于标记某个顶点是否找到最短路径
int dis[vertex_MAX]; //记录顶点得路径长
int path[vertex_MAX]; //记录最短路径时的直接前驱下
}Dijkstra;
这三个辅助向量来帮助我们有效执行迪杰斯特拉算法
算法执行流程
首先我们要先将我所要求到其他顶点最短路径的那个顶点录入final数组,因为这个顶点到他自己的路径永远是0,且已经最小,因此将它自己的final置为true;
随后,我们要找到所有从这个顶点指向出的弧,以第一次对dis数组初始化
void Dijkstra_DN(MGraph* G,Dijkstra* D, int pos)
{
D->final[pos] = true;
for (int i = 0; i < G->vexnum; i++)
{
D->dis[i] = MAX;//当他为MAX时标识两节点无直接路径
D->path[-1];
}
/*初始化辅助向量*/
for (int w = FirstPoint(G, pos); w > 0; w = NextPoint(G, pos, w))
{
D->dis[w] = G->arcs[pos][w].adj;
D->path[w] = pos;
}
}
以如下图为例,我们要求v0指到图中其他所有节点的最短路径,在经历第一步算法之后
辅助向量集变成以下形式
之后根据前面我们推导过的定理一(多个直接相连的节点中权最小的是最短路径)
我们得出v4指向v0的是最短路径
此时对v4进行修饰
此时已经确定
的最小情况,之后我们还要求P01 P02 P03 的最小路径,所以我们可以以v4作为根节点进行判断:
如果出现有一个与v4近邻节点,final为false,且其到v4的距离 + v4到v0的距离小于当前其dis数组的距离,就说明
P0x < P04 + P4x
因此要修正dis的值
修正后辅助向量集如下图
这时观察到v3的dis最小,再确定v3,同时修正辅助向量集
之后观察到v1最小,修正v1为,同时修正辅助向量集
之后就只剩下v2了,修正v2,同时修饰辅助向量集
完成手算的算法流程
手算过程总结
手算的核心在于,判断哪个节点需要修正,具体的修正过程
代码实现
我们先来看看之前手算的流程图
不难发现,我们总是在重复修正-初始化这个流程,所以我们可以从中抽象出两个函数
int WhichNeedChange(MGraph* G, Dijkstra* D); //找到需要修正的顶点,并返回其下标
void Revise(MGraph* G, Dijkstra* D, int w); //以w节点为根修正辅助向量
所以我们的程序可以简化称如下形式
void Dijkstra_DN(MGraph* G,Dijkstra* D, int pos)
{
D->final[pos] = true;
for (int i = 0; i < G->vexnum; i++)
{
D->dis[i] = MAX;//当他为MAX时标识两节点无直接路径
D->path[-1];
}
/*初始化辅助向量*/
for (int w = FirstPoint(G, pos); w > 0; w = NextPoint(G, pos, w))
{
D->dis[w] = G->arcs[pos][w].adj;
D->path[w] = pos;
}
/*上面是初始化v0即辅助向量*/
for (int i = 0; i < G->vexnum - 1; i++)
{
int w = WhichNeedChange(D);
Revise(G, D, w);
}
}
WhichNeedChange函数的实现
int WhichNeedChange(MGraph* G,Dijkstra* D)//找到需要修正的顶点,并返回其下标
{
int min = MAX;
int ret = -1;
for (int i = 0; i < G->vexnum; i++)
{
if (D->final[i] == false && D->dis[i] < min)
{
ret = i;
min = D->dis[i];
}
}
D->final[ret] = true;
return ret;
}
这个函数难点在于理解中间的if判断过程
需要修正的顶点需要满足:
1,他需要没有被确定过最短路径
2,他的距离要是最小值
Revis修正函数的实现
让我们再来回顾以下修正函数的执行思路
第一步,先找出给定节点的所有指向节点
第二步,看这些新找到节点路径+给定节点的路径 是否小于当前节点的路径
即 判断是否有 P0x < P04 + P4x
void Revise(MGraph* G, Dijkstra* D, int w)//以w节点为根修正辅助向量
{
for (int v = FirstPoint(G, w); v > 0; v = NextPoint(G, w, v))
{
if (G->arcs[w][v].adj + D->dis[w] < D->dis[v])
{
D->dis[v] = G->arcs[w][v].adj + D->dis[w];
D->path[v] = w;
}
}
}
理解这个函数的难点再与理解if语句
这个语句的意思是,如果新开辟的路径加上给定节点到v0的路径小于原有节点到v0的路径,就需要添加一个中间节点,以达到求出最短路径的目的
整体代码总览
算法核心代码
int FirstPoint(MGraph* G, int v)
{
int ret = -1;
for (int j = 0; j <= G->vexnum; j++)
{
if (G->arcs[v][j].adj != MAX)
{
ret = j;
break;
}
}
return ret;
}
int NextPoint(MGraph* G, int v, int w)
{
int ret = -1;
for (int j = w; j <= G->vexnum; j++)
{
if (G->arcs[v][j].adj != MAX)
{
ret = j;
break;
}
}
return ret;
}
int WhichNeedChange(MGraph* G,Dijkstra* D)//找到需要修正的顶点,并返回其下标
{
int min = MAX;
int ret = -1;
for (int i = 0; i < G->vexnum; i++)
{
if (D->final[i] == false && D->dis[i] < min)
{
ret = i;
min = D->dis[i];
}
}
D->final[ret] = true;
return ret;
}
void Revise(MGraph* G, Dijkstra* D, int w)//以w节点为根修正辅助向量
{
for (int v = FirstPoint(G, w); v > 0; v = NextPoint(G, w, v))
{
if (G->arcs[w][v].adj + D->dis[w] < D->dis[v])
{
D->dis[v] = G->arcs[w][v].adj + D->dis[w];
D->path[v] = w;
}
}
}
void Dijkstra_DN(MGraph* G,Dijkstra* D, int pos)
{
D->final[pos] = true;
for (int i = 0; i < G->vexnum; i++)
{
D->dis[i] = MAX;//当他为MAX时标识两节点无直接路径
D->path[-1];
}
/*初始化辅助向量*/
for (int w = FirstPoint(G, pos); w > 0; w = NextPoint(G, pos, w))
{
D->dis[w] = G->arcs[pos][w].adj;
D->path[w] = pos;
}
for (int i = 0; i < G->vexnum - 1; i++)
{
int w = WhichNeedChange(G,D);
Revise(G, D, w);
}
}
临接矩阵存储结构以及初始化代码
#include <stdio.h>
#define vertex_MAX 20
#define MAXSIZE 10
#define VRType int
#define VertexType char
#define MAX 99999
typedef struct Dijkstra
{
bool final[vertex_MAX]; //用于标记某个顶点是否找到最短路径
int dis[vertex_MAX]; //记录顶点得路径长
int path[vertex_MAX]; //记录最短路径时的直接前驱下
}Dijkstra;
typedef enum {
DG, //有向图
DN, //有向网
UDG, //无向图
UDN, //无向网
}GraphKind; //图的类型
typedef struct ArcCell
{
VRType adj; //矩阵元素的值,如果是网则是具体的值;如果是图则只有1,0两种
}ArcCell;
typedef struct MGraph
{
GraphKind kind; //图的类型
int vexnum; //定点数
int arcnum; //边数
VertexType vexs[MAXSIZE];
ArcCell arcs[MAXSIZE][MAXSIZE];
}MGraph;
void creatGraph_DN(MGraph* G)
{
G->kind = DN;
printf("请输入网的顶点数\\n");
scanf("%d", &(G->vexnum));
printf("请输入网的边数\\n");
scanf("%d", &(G->arcnum));
for (int i = 0; i < G->vexnum; i++)
{
scanf("%c", G->vexs[i]);
}
getchar();
/*顶点录入完毕*/
for(int i = 0;i<G->vexnum;i++)
for (int j = 0; j < G->vexnum; j++)
{
G->arcs[i][j].adj = MAX;
}
/*临接矩阵初始化完毕*/
for (int k = 0; k < G->arcnum; k++)
{
int i, j, w;
printf("请输入弧<i,j>及其权w\\n");
scanf("%d %d %d", &i, &j, &k);
G->arcs[i][j].adj = w;
}
/*各边及其权值录入完毕*/
}
文末总结
迪杰斯特拉算法的实质就是不断重复《找待修正节点》 —《修正辅助向量》这一过程
只要把握了这个特点,理解迪杰斯特拉算法就不难了
在研究生考试的初试中,我们只需要掌握迪杰斯特拉算法的手算即可!!
以上是关于一篇文章让你搞懂Dijikstra(迪杰斯特拉)算法的主要内容,如果未能解决你的问题,请参考以下文章