Dijkstra算法代码详解(C语言)

234 阅读8分钟

Dijkstra算法代码详解(C语言)

一、算法介绍及其总体思想

Dijkstra算法通常用来求一个点到其他点的最短路径。 可以算是贪心算法的一种,其基本思想为:从问题的某个初始解出发,通过一系列的局部最优选择,从而得到全局的最优解。

二、算法流程

以下图为例,分别求0点到1,2,3,4点的最短路径:

路径图.png


首先,引入四个数组:

graph[Maxsize][Maxsize] : 用于创建图

d[i] : 源点到各个顶点的最短距离

visit[i] : 标记顶点i是否已经求得最短路径,1表示已求得,0表示未求得

p[i] : 源点到顶点i的最短路径上i的前一个顶点(前驱顶点)


第一步——创建图并初始化

(1)

#include <stdio.h>
#define INF 10000000
#define MAXSize 100// 最大顶点数
int graph[MAXSize][MAXSize];// 图的邻接矩阵
int main() {
	int n, m;
	printf("请输入顶点数和边数:(用空格分开它们)\n");
	scanf_s("%d %d", &n, &m); // n:顶点数   m:边数

	int u, v, w;
	// u:边的起始点   v:边的终点  w:边的权重(即长度)

(2)图的初始化

	// 初始化图的邻接矩阵
	for (int i = 0; i < n; i++) 
	{
		for (int j = 0; j < n; j++)
		{
			if(i==j)
				graph[i][j] = 0; //表示每一个顶点到自己的距离为0
			else
				graph[i][j] = INF; //表示每一个顶点都是单点,各不相连
		}
	}
i01234
00INFINFINFINF
1INF0INFINFINF
2INFINF0INFINF
3INFINFINF0INF
4INFINFINFINF0

经过这一步后,图的样子:

单点图.png

再继续初始化:

	printf("请分别输入边的起始点、终点、长度:(用空格分开它们)\n");
	// 读入边的信息
	for (int i = 0; i < m; i++)
	{
		scanf_s("%d %d %d", &u, &v,&w); 
		// u:边的起始点   v:边的终点  w:边的权重(即长度)
		graph[u][v] = w;
		graph[v][u] = w;
	}
}

经过这一步后,邻接矩阵:

i01234
0037INFINF
1INF05INF9
2INFINF05INF
3INFINFINF0INF
4INFINFINF60

经过这一步后,图的样子:

路径图.png

第二步

遍历除0外的所有其他点,确定原点0与各点i的直接距离d[i]。从而找出离原点0最近的点的最短路径

如图:

第一步.png

  • d[0] = 0 , d[1] = 3 , d[2] = 7 , d[3] = INF(无穷大) , d[4] = INF
  • visit = {1,0,0,0,0} (0点到0点本身的距离已经知道了,就是0,故visit[0]=1)
i01234
d [ i ]037INFINF
visit [ i ]10000
p [ i ]00000

比较d[ i ] (i>0,因为原点本身不算在内)各值,找到点1最短路径为0->1,可得visit [1] =1。

第三步

又以已经找到最短路径的点(记为点target_index)为新的起始点(在本例中为点1),比较d[ i ]与d[ target_index ]+graph[ target_index ][ i ]

(graph[ target_index ][ i ]为新起始点到除前驱顶点以外的其他点的距离)

若d[i] > d[target_index] + graph[target_index][i],则更新源点0到顶点i的最短距离

重复这个算法直到每个最短路径被找到。

void dijkstra(int n) {
	int count = 0;
	//count是已经求得最短路径的顶点数

	visit[0] = 1;
	//这行代码表示源点0已经求得最短路径,其最短路径长度为0

	p[0] = 0;
	//意思是:源点0没有前驱点

	count++; 
	//已求得最短路径的顶点数加1,这个顶点就是源点0

	//初始化d[i]为源点0到顶点i的距离
	for (int i = 1; i < n; i++) 
	{
		d[i] = graph[0][i]; //源点0到顶点i的距离
		p[i] = 0; //源点0到顶点i的最短路径上i的前一个顶点,就是源点0
	}

	//循环n-1次,每次求得一个顶点的最短路径
	while (count<n)
	{
		int min = INF; 
		int target_index=-1 ; //target_index是下一个求得最短路径的顶点
		
		//先找到离源点0最近的顶点
		for (int i = 1; i < n; i++) 
		{
			if (visit[i] == 0 && min > d[i])//找到一个离源点0最近的顶点
			{
				min= d[i];
				target_index = i;
			}

			
		}
		visit[target_index] = 1;//标记顶点target_index已经求得最短路径
		count++;

		//更新源点0到顶点i的最短距离
		for (int i = 1; i < n; i++)
		{
			if (visit[i] == 0 && d[i] > d[target_index] + graph[target_index][i])
			{
				d[i] = d[target_index] + graph[target_index][i];
				//更新源点0到顶点i的最短距离

				p[i] = target_index;
				//更新源点0到顶点i的最短路径上i的前一个顶点
			}
		}
	}
}

三、完整代码

#include <stdio.h>
#define INF 10000000
#define MAXSize 100// 最大顶点数

int graph[MAXSize][MAXSize];
// 图的邻接矩阵

int d[MAXSize]; 
// d[i]为源点到各个顶点的最短距离

int visit[MAXSize]; 
// visit[i]标记顶点i是否已经求得最短路径
// 在这个数组中,1表示已求得最短路径,0表示未求得最短路径,相当于一个布尔数组

int p[MAXSize]; 
// p[i]为源点到顶点i的最短路径上i的前一个顶点


void dijkstra(int n) {
	int count = 0;
	//count是已经求得最短路径的顶点数

	visit[0] = 1;
	//这行代码表示源点0已经求得最短路径,其最短路径长度为0

	p[0] = 0;
	//意思是:源点0没有前驱点

	count++; 
	//已求得最短路径的顶点数加1,这个顶点就是源点0

	//初始化d[i]为源点0到顶点i的距离
	for (int i = 1; i < n; i++) 
	{
		d[i] = graph[0][i]; //源点0到顶点i的距离
		p[i] = 0; //源点0到顶点i的最短路径上i的前一个顶点,就是源点0
	}

	//循环n-1次,每次求得一个顶点的最短路径
	while (count<n)
	{
		int min = INF; 
		int target_index=-1 ; //target_index是下一个求得最短路径的顶点
		
		//先找到离源点0最近的顶点
		for (int i = 1; i < n; i++) 
		{
			if (visit[i] == 0 && min > d[i])//找到一个离源点0最近的顶点
			{
				min= d[i];
				target_index = i;
			}

			
		}
		visit[target_index] = 1;//标记顶点target_index已经求得最短路径
		count++;

		//更新源点0到顶点i的最短距离
		for (int i = 1; i < n; i++)
		{
			if (visit[i] == 0 && d[i] > d[target_index] + graph[target_index][i])
			{
				d[i] = d[target_index] + graph[target_index][i];
				//更新源点0到顶点i的最短距离

				p[i] = target_index;
				//更新源点0到顶点i的最短路径上i的前一个顶点
			}
		}
		
		
	}
}

void printDijkstra(int n) {
	int path[MAXSize]; //path数组用来展示顶点0到顶点i的最短路径上的各顶点
	for (int i = 1; i < n; i++)
	{
		if (d[i] == INF)
		{
			printf("顶点0到顶点%d没有最短路径\n", i);
		}
		else
		{
			printf("顶点0到顶点%d的长为%d最短路径为:",i,d[i]);
			int cur = i;
			int index = 0;
			// 初始化当前节点(cur)为目标节点i,
			// 并设置路径数组(path)的索引(index)为0。

			path[index] = cur; //将终点cur加入到path数组中
			
			//这个循环的目的是沿着最短路径从目标节点i回溯到源点(顶点0)
			while (1)
			{
				path[index+1]= p[path[index]];
				// 找到当前节点的前驱节点,并将其添加到路径数组(path)中
				
				if (path[index + 1] == 0)
				//条件判断是检查是否已经回溯到源点(顶点0)
				{
					break;
				}
				index++;//更新路径数组(path)的索引(index)
			}
			// 这个for循环用来打印出从源点(顶点0)到目标节点i的最短路径
			for (int j = index+1; j > 0; j--)
			{
				printf("%d->",path[j]);
				// 这行代码打印出路径中各节点。
				// 注意,由于路径是从目标节点i回溯到源点(顶点0)得到的,
				// 所以打印路径时需要从后往前打印
			}
			printf("%d\n",path[0]);
			// 这行代码是打印出路径的最后一个节点(也就是目标节点i)
		}
	}
}

int main() {
	int n, m;
	printf("请输入顶点数和边数:(用空格分开它们)\n");
	scanf_s("%d %d", &n, &m); // n:顶点数   m:边数

	int u, v, w;
	// u:边的起始点   v:边的终点  w:边的权重(即长度)(必须非负)

	// 初始化图的邻接矩阵
	for (int i = 0; i < n; i++) 
	{
		for (int j = 0; j < n; j++)
		{
			if(i==j)
				graph[i][j] = 0; //表示每一个顶点到自己的距离为0
			else
				graph[i][j] = INF; //表示每一个顶点都是单点,各不相连
		}
	}
	
	printf("请分别输入边的起始点、终点、长度:(用空格分开它们)\n");
	// 读入边的信息
	for (int i = 0; i < m; i++)
	{
		scanf_s("%d %d %d", &u, &v,&w); 
		// u:边的起始点   v:边的终点  w:边的权重(即长度)
		graph[u][v] = w;
		graph[v][u] = w;
	}

	dijkstra(n);
	printDijkstra(n);
}

输入输出范例:

请输入顶点数和边数:(用空格分开它们)

(输入) 5 6

请分别输入边的起始点、终点、长度:(用空格分开它们)

(输入)

0 1 3

0 2 7

1 2 5

1 4 9

2 3 5

4 3 6

(输出)

顶点0到顶点1的长为3最短路径为:0->1

顶点0到顶点2的长为7最短路径为:0->2

顶点0到顶点3的长为12最短路径为:0->2->3

顶点0到顶点4的长为12最短路径为:0->1->4