迪杰斯特拉(dijkstra)算法

在讲代码以前,先了解dijkstra算法的思想以及注意事项:

核心思想

算法的核心思想是贪心算法。它通过逐步确定从源点到图中其他所有顶点的最短路径。

1.将顶点集合分为两组:

a. 已确定最短路径的顶点集合(U):初始时,这个集合只包含源点本身。

b. 未确定最短路径的顶点集合(V):初始时,包含除源点外的所有顶点。

  1. 从集合 V 中选出当前"距离"源点最近的顶点,将其加入集合 U。

  2. 更新集合V中,这个新加入顶点的所有邻居到源点的"当前最短距离估计值"。这个过程称为"松弛"操作。

  3. 重复步骤 2 和 3,直到所有顶点都包含在集合 U 中,即找到了源点到所有顶点的最短路径。

重要前提

  • 非负权边 :迪杰斯特拉算法要求图中所有边的权值都为非负数(例如,不能有负数距离或成本)。如果存在负权边,算法可能无法得出正确结果。

  • 单源:它计算的是从一个固定的源点出发到所有其他点的最短路径。


步骤解析

以上图为例,先定义顶点s作为起始点,此时更新能够到达的边距离(这里INT_MAX表示无穷大):

s->y:5 s->t:10 s->z:INT_MAX s->x:INT_MAX

这里我们要注意,dijkstra算法是只适用于负权值的有向网

在将起始点加入以后,肯定会选择一条相邻的最短路径边进入已确定最短路径的顶点集合U ,每添加一个顶点到U集合以后,更新源点到V集合(未确定最短路径的顶点集合)中的各顶点的最短路径估计值

将y添加到集合U中,更新到V集合中的点,此时可以到达的顶点t更新为8(5+3)、顶点z更新为7(5+2)、顶点x更新为14(5+9),

选择V集合中最短一条边添加对应顶点到U集合后,更新V集合;这里将z添加到U集合后,从源点s顶点x的距离现在为13(5+2+6)比原来的14(5+9)小,更新集合V中顶点x的数据。

然后重复2、3步骤,选择集合V中的最短一条边,添加对应点t到集合U,更新集合V,从源点s到x的距离可以为9(5+3+1)比上一次更新后集合V中的距离13(5+2+6)更小,那么更新集合V中x的数据

最后所有顶点均被添加至集合U中,集合U中所存数据即为最短路径。


代码准备

判断顶点是否已被添加至集合U:和之前Prim算法思路类似,定义一个bool类型的数组,名为sptset,初值为false,每添加一个,就更新对应顶点下标的sptset数组元素为true,表示已经添加到集合U;定义一个名为parent的数组,记录当前下标对应的顶点,它的父顶点,比如

下标2对应的是顶点表中下标为2的元素,下标为1对应的是顶点表中下标为1的元素,只不过parent类型的值存的是它父节点的下标,也就是说parent的父节点是下标1对应的元素......

dist数组:存储集合V的数据,更新源点到各个顶点之间的距离估计


代码

先用for循环对g->vertexNum个顶点的对应下标数组初始化,然后源点(起点距离初始化为0)

cpp 复制代码
void Dijkstra(Graph* g,int src) {
	bool* sptSet = malloc(sizeof(bool) * g->vertexNum);
	int* parent = malloc(sizeof(int) * g->vertexNum);

	//类似集合v
	int* dist = malloc(sizeof(int) * g->vertexNum);
	for (int i = 0; i < g->vertexNum; i++) {
		sptSet[i] = false;
		parent[i] = -1;//表示没有父亲,也可以初始化为i,自己是自己的下标
		dist[i] = INT_MAX;
	}
	dist[src] = 0;

用两段for循环模拟步骤二、三:

第一层for循环是在集合v中寻找最短路径,添加到集合U(sptSet数组)中,设置对应下标元素为true,表示已经添加,第二段代码进行松弛操作,将符合要求的顶点更新:顶点不在集合U中,顶点v到顶点j之间存在一条有向路径(因为添加到集合U中的一个顶点对应下标为v,如果v和下标j之间有一条路径,进行第三条判断),最短路径(源点->下标v顶点)+ 下标v顶点->下标j顶点 距离之和小于 源点直接->下标j顶点的距离,就更新集合V,(因为下次还从集合V中选)

cpp 复制代码
//松弛操作
for (int i = 0; i < g->vertexNum - 1; i++) {
	//从集合V的顶点中,找一个离源点最近的顶点加入U集合中
	int v = minDistance(dist, sptSet,g->vertexNum-1);

	//添加这个顶点到U集合中
	sptSet[v] = true;

	//松弛操作
	//已经加入到U集合的顶点就不用再做松弛操作,因为他们一定是各自到源点的最短距离
	for (int j = 0; j < g->vertexNum; j++) {
		//先判断要访问的顶点是否已经加入U集合
		//顶点下标v到j必须有值,两点之间必须存在路径才行
		//如果源点到v,加上v到j的距离小于源点到j的距离,将集合v更新;
		if (!sptSet[j] && g->edge[v][j]!=INT_MAX && dist[j] > dist[v] + g->edge[v][j]) {
			dist[j] = dist[v] + g->edge[v][j];
			parent[j] = v;//记录源点到 [下标j顶点] 的路径,记录[下标j顶点]的父亲节点
		}
	}
}

大致完整代码:

cpp 复制代码
int minDistance(int* dist, bool* sptSet,int n) {
	int min = INT_MAX;
	int minPos = -1;
	for (int i = 0; i < n; i++) {
		if (!sptSet[i] && dist[i] < min) {//false取反为true
			min = dist[i];
			minPos = i;
		}
	}//for循环寻找距离源点最短的顶点

	//返回最短路径对应顶点下标
	return minPos;
}



void printPath(int* parent, int v) {
	if (parent[v] == -1) {
		printf("%d", v);
		return;
	}
	printPath(parent, parent[v]);
	printf("->%d", v);
}

void Print(int* parent, int* dist, int src, int n) {
	printf("源点%d到各个顶点的最短路径:\n", src);
	printf("目标顶点\t最短距离\t路径\n");
	for (int i = 0; i < n; i++) {
		if (src != i) {
			printf("%d\t\t%d\t\t", i, dist[i]);
			printPath(parent, i);
			printf("\n");
		}
	}
}

/// <summary>
/// 迪杰斯特拉算法
/// </summary>
/// <param name="g">邻接表</param>
/// <param name="src">源点下标</param>
void Dijkstra(Graph* g,int src) {
	bool* sptSet = malloc(sizeof(bool) * g->vertexNum);
	int* parent = malloc(sizeof(int) * g->vertexNum);

	//类似集合v
	int* dist = malloc(sizeof(int) * g->vertexNum);
	for (int i = 0; i < g->vertexNum; i++) {
		sptSet[i] = false;
		parent[i] = -1;//表示没有父亲,也可以初始化为i,自己是自己的下标
		dist[i] = INT_MAX;
	}
	dist[src] = 0;

	//松弛操作
	for (int i = 0; i < g->vertexNum - 1; i++) {
		//从集合V的顶点中,找一个离源点最近的顶点加入U集合中
		int v = minDistance(dist, sptSet,g->vertexNum-1);

		//添加这个顶点到U集合中
		sptSet[v] = true;

		//松弛操作
		//已经加入到U集合的顶点就不用再做松弛操作,因为他们一定是各自到源点的最短距离
		for (int j = 0; j < g->vertexNum; j++) {
			//先判断要访问的顶点是否已经加入U集合
			//顶点下标v到j必须有值,两点之间必须存在路径才行
			//如果源点到v,加上v到j的距离小于源点到j的距离,将集合v更新;
			if (!sptSet[j] && g->edge[v][j]!=INT_MAX && dist[j] > dist[v] + g->edge[v][j]) {
				dist[j] = dist[v] + g->edge[v][j];
				parent[j] = v;//记录源点到 [下标j顶点] 的路径,记录[下标j顶点]的父亲节点
			}
		}
	}
	//打印parent数组即为顶点相连顺序;
	Print(parent, dist, src,g->vertexNum);
}

主函数:

cpp 复制代码
int main()
{
	vertexType arr[] = { 's','y','z','t','x' };
	Graph g;
	initGraph(&g, arr, 5, 10);
	addEdge(&g, 's', 't', 10);
	addEdge(&g, 's', 'y', 5);
	addEdge(&g, 'y', 't', 3);
	addEdge(&g, 'y', 'z', 2);
	addEdge(&g, 'y', 'x', 9);
	addEdge(&g, 'z', 'x', 6);
	addEdge(&g, 'z', 's', 7);
	addEdge(&g, 't', 'y', 2);
	addEdge(&g, 't', 'x', 1);
	addEdge(&g, 'x', 'z', 4);
	printGraph(&g);
	//DFS(&g);
	putchar('\n');
	//BFS(&g);
	//minSpanTree_Prim(&g);
	Dijkstra(&g, 0);
	return 0;
}

最终结果:

完整代码请跳转至:hjh0127/gitee查看Dijsktra.c文件

相关推荐
南方的狮子先生3 小时前
【数据结构】从线性表到排序算法详解
开发语言·数据结构·c++·算法·排序算法·1024程序员节
派大星爱吃猫3 小时前
快速排序和交换排序详解(含三路划分)
算法·排序算法·快速排序·三路划分
焜昱错眩..3 小时前
代码随想录第四十八天|1143.最长公共子序列 1035.不相交的线 53. 最大子序和 392.判断子序列
算法·动态规划
AI妈妈手把手4 小时前
YOLO V2全面解析:更快、更准、更强大的目标检测算法
人工智能·算法·yolo·目标检测·计算机视觉·yolo v2
极客智造4 小时前
编程世界的内在逻辑:深入探索数据结构、算法复杂度与抽象数据类型
数据结构·算法·数学建模
好好学习啊天天向上4 小时前
多维c++ vector, vector<pair<int,int>>, vector<vector<pair<int,int>>>示例
开发语言·c++·算法
MicroTech20254 小时前
MLGO微算法科技 LOP算法:实现多用户无线传感系统中边缘协同AI推理的智能优化路径
人工智能·科技·算法
Greedy Alg5 小时前
Leetcode 279. 完全平方数
算法
剪一朵云爱着5 小时前
力扣410. 分割数组的最大值
算法·leetcode