一篇文章让你搞懂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(迪杰斯特拉)算法的主要内容,如果未能解决你的问题,请参考以下文章

(王道408考研数据结构)第六章图-第四节4:最短路径之迪杰斯特拉算法(思想代码演示答题规范)

图解迪杰斯特拉(Dijkstra)最短路径算法

求多重邻接表的迪杰斯特拉算法

迪杰斯特拉算法为啥不能有负权边

图的应用——最短路径(迪杰斯特拉算法)

迪杰斯特拉算法的算法思想