数据结构_图的最短路径(Dijkstra和Folyd算法)

在平时开车的时候,去一个地方总是用的路线规划软件,对于路线的规划中,有行驶路程最短的,有经过收费站最少的。根据不同的需求,对于路径长度的度量做出改变,以经过收费站数目作为度量(图中将收费站表示为顶点),就是最简单的最短路径问题,用图的广度优先搜索就可以实现。而为了实际表达现实交通注重的行驶路程的代价或时间,常用有向连通网表示,习惯将路径上的第一个顶点称为源点,最后一个称为终点

常见的最短路径问题:

  1. 从某个源点到其余顶点的最短路径问题
    给定带权有向图G和源点v0, 来求从v0到其余各个顶点的最短路径。Dijkstra提出的是一个按路径长度递增的次序产生最短路的算法。
  2. 每一对顶点之间的最短路径问题
    求解每一对顶点之间的最短路径问题有两种方法:
    ①分别以图中的每一个顶点共调用n次Dijstra 算法
    ②是Folyd算法
    但是两种算法的时间负责度均为O(nnn),但Folyd形式上相对比较简单。

两个算法的实现建立在图的存储结构为邻接矩阵的基础上,相关知识可参考本专栏内容—— 数据结构_图.

Dijkstra 算法

算法思想

Dijkstra算法的求解过程

  1. 对于有向网N=(V, E),将N中的顶点分成两组:
    第一组S:已求出的最短路径的终点集合(初始时只包含源点v0)。
    第二组V−S:尚未求出的最短路径的顶点集合(初始时为V−{v0})。
  2. 按各顶点与v0间最短路径长度递增的次序,逐个将集合V−S中的顶点加入到集合S中去。在这个过程中,总保持从v0到集合S中各顶点的路径长度始终不大于到集合V−S中各顶点的路径长度。——即现在加入的是某一顶点vi∈S,下一个加入的顶点vj∈V-S。因为顶点是按照边的递增选择,所以(v0,vi)一定小于(v0,vj)的路径长度。((v0,vi)表示的是v0到vi的最短路径,不一定是弧<v0,vi>的路径长度,可能存在一个中继顶点使得{v0,vk,vi}这样一条路径小于并更新(v0,vi)的路径长度,但是不管更新与否始终小于<v0,vj>的路径长度)

代码实现

Dijkstra算法的实现

  1. 使用图的邻接矩阵E表示有向带权连通网G,E[i][j]表示弧<vi, vj>上的权值。若<vi, vj>不存在,则置E[i][j]为∞,源点为v0。
  2. 借助的数据结构
    ①一维数组S[i]:记录从源点v0到终点vi是否已被确定最短路径长度,true表示确定,false表示尚未确定。(由于选择边是选择最小边,对于已经加入S集合的顶点记录需要使用数组)
    ② 一维数组Path[i]:记录从源点v0到终点vi的当前最短路径上vi的直接前驱顶点序号(即若存在中继顶点,值为中继顶点)。其初值为:如果从v0到vi有弧,则Path [i]为v0;否则为−1。(如果存在一条顶点序号为{1,4,6}的最短路径,那么Path[6] = 4, Path[4] = 1,Path[1] = -1)
    ③ 一维数组D[i]:记录从源点v0到终点vi的当前最短路径长度。其初值为:如果从v0到vi有弧,则D[i]为弧上的权值;否则为∞。
  3. 实现步骤:
    ①选择路径长度最短的路径(v0, vk),并将vk加入到顶点集S中。
    ②每加入新的顶点到顶点集S,对V-S顶点来说,可能存在这个顶点作为中继顶点,来更新路径。 即原来v0到vi的最短路径长度为 D[ i ],加进vk之后,以vk作为中继顶点,存在D[k]+E[k][i],使D[k]+E[k][i]<D[i],则用D[k]+E[k][i]值取代D[i]。
    ③重复①
/// ===============================================
/// Dijstra算法实现
/// ===============================================

bool S[MAX];           //记录顶点的访问情况,已访问的置为ture
int Path[MAX];         //记录源点到该顶点路径中的尾顶点的上一个顶点序号
int D[MAX];            //记录单源最短路径的路径长度


void Dijstra(G_Amatreix g,int v) {
	for (int i = 0; i < g.n; ++i) {        //用源点将各个辅助数据结构初始化
		S[i] = false;                     
		D[i] = g.E[v][i];                  
		if (D[i] < Maxint)
			Path[i] = v;
		else
			Path[v] = -1;
	}

	S[v] = true;                            //将源点的辅助数据结构置数
	D[v] = 0;

	for (int i = 1; i < g.n; ++i) {         //依次扫描下n-1个顶点
		int min = Maxint;
		for (int j = 0; j < g.n; ++j) {     //找出最小权值边加入顶点集合 S[]
			if (D[j] < min && !S[j]) {
				v = j;
				min = D[j];
			}
		}

		S[v] = true;
		
		for (int k = 0; k < g.n; ++k) {        //找出可以用该顶点所在路径可以更新剩余顶点的最短路径
			if (!S[k] && D[v] + g.E[v][k] < D[k]) {
				D[k] = D[v] + g.E[v][k];
				Path[k] = v;
			}
		}
		
	}

}

测试
在这里插入图片描述

在这里插入图片描述

Folyd 算法

算法思想

Floyd的算法思想

  1. 辅助的数据结构:
    ①二维数组Path[i][j]:最短路径上顶点vj的前驱顶点的序号。
    ②二维数组D[i][j]:记录顶点vi和vj之间的最短路径长度。
    将vi到vj的最短路径长度初始化,即D[i][j]=G.arcs[i][j],然后进行n次比较和更新。
  2. 更新实现过程:
    (1)
    ① 在 vi 和 vj 间加入顶点 v0
    ②比较 {vi, vj} 和 {vi, v0, vj} 的路径长度
    ③取其中较短者作为 vi 到 vj 的中间顶点序号不大于0的最短路径。
    (2)
    ①再在 vi 和 vj 间加入顶点 v1。而 {vi,…, v1} 和 {v1,…, vj} 是(1)中求出来的中间顶点的序号不大于0的最短路径 ,合并得 {vi,…, v1, …,vj} (若(1)中没有更新就是 {vi, v1,vj} ,反之就是{ {vi, v0,v1,v0,vj}, {vi, v0,v1,vj}, {vi,v1,v0,vj} }中一种)
    ②比较 {vi,…, v1,…, vj} 与(1)求出的 {vi, (v0) ,vj} 比较 (根据(1)得结果,v0可能是最短路径中得中间顶点,也可能不是)
    ③取其中较短者作为vi到vj的中间顶点序号不大于1的最短路径。
    (3)
    ① 依次类推,在 vi 和 vj 间加入顶点 vk 。而 {vi,…, vk} 和 {vk,…, vj} 分别是从 vi 到 vk 和从 vk 到 vj 的中间顶点的序号不大于k−1的最短路径
    ②比较(vi,…, vk,…, vj)和上一步得到的从vi到vj且中间顶点序号不大于k−1的最短路径相比较
    ③其长度较短者便是从vi到vj的中间顶点的序号不大于k的最短路径。
    (4)
    这样,经过n次比较后,得是从vi到vj的最短路径,其路径值存储于D[i][j] , 前驱顶点存储于Path[i][j]。
  3. 最后得到得D[ i ][ j ]可以表示为一个n阶方阵D
    D(−1)[ i ][ j ] = G.arcs[ i ][ j ]
    D(k)[ i ][ j ] = Min{ D(k−1)[ i ][ j ],D(k−1)[ i ][ k ] + D(k−1)[ k ][ j ] } 0≤k≤n−1
    D(k)[i][j]是从vi到vj的中间顶点的序号不大于k(也就是加入中间顶点vk)的最短路径的长度

代码实现

主要函数

/// ===============================================
/// Folyd算法实现
/// ===============================================

int Path1[MAX][MAX];         //记录源点到该顶点路径中的尾顶点的上一个顶点序号
int D1[MAX][MAX];            //记录最短路径的路径长度


void Floyd(G_Amatreix g) {
	for (int i = 0; i < g.n; ++i)          //用邻接矩阵将各个辅助数据结构初始化
		for (int j = 0; j < g.n; ++j) {    
			D1[i][j] = g.E[i][j];
			if (D1[i][j] < Maxint)
				Path1[i][j] = i;
			else
				Path1[i][j] = -1;
		}
		

	for (int k = 0; k < g.n; ++k)           //依次使用中间顶点对于最短路径的更新
		for (int i = 0; i < g.n; ++i)   
			for (int j = 0; j < g.n; ++j) {
				if ((D1[i][k] + D1[k][j] < D1[i][j]) && (i!=j)) {
					D1[i][j] = D1[i][k] + D1[k][j];
					Path1[i][j] = Path1[k][j];
				}
			}
}

测试
在这里插入图片描述

在这里插入图片描述在这里插入图片描述

完整代码

所需头文件

///GAmatrix.h
#pragma once
#include<stdio.h>
#define Maxint 32767
#ifndef MAX
#define MAX 20
#endif // MAX

typedef char VertexType;
typedef int Edgetype;

typedef struct {
	VertexType V[MAX];
	Edgetype E[MAX][MAX];
	int n, e;
}G_Amatreix;

//创建图
void CreateG(G_Amatreix& g);

//找到顶点在顶点集中的标号
int LocateV(G_Amatreix g, VertexType v);

测试c文件

#include<stdio.h>
#include"GAmatrix.h"

/// ===============================================
/// 图邻接矩阵接口实现
/// ===============================================
//创建图
void CreateG(G_Amatreix& g) {
	printf("input n , e:\n");
	scanf_s("%d,%d", &g.n, &g.e);
	getchar();
	//初始化V
	for (int i = 0; i < g.n; ++i) {
		printf("input V[%d]:", i);
		scanf_s("%c", &g.V[i], 1);
		char ch = getchar();
		while (ch != '\n')
			ch = getchar();
	}
	//初始化E
	for (int i = 0; i < g.n; ++i) {
		for (int j = 0; j < g.n; ++j) {
			g.E[i][j] = Maxint;                   //创立网是为无穷大,图时为0
		}
	}
	//输入边
	char v1 = 0, v2 = 0;
	int m = 0;;
	int j, k;
	for (int i = 0; i < g.e; ++i) {
		printf("input the edge:(v1 v2 w)\n");
		scanf_s("%c %c %d", &v1, 1, &v2, 1, &m);
		char ch = getchar();
		while (ch != '\n')
			ch = getchar();
		j = LocateV(g, v1);
		k = LocateV(g, v2); 
		g.E[j][k] = m;                      //创立网时为权值w,图时为1
		//g.E[k][j]=g.E[j][k];                 //无向时为对称矩阵

	}


	输出边
	//for (int i = 0; i < g.n; ++i) {
	//	printf("顶点%c:\n", g.V[i]);
	//	for (int j = 0; j < g.n; ++j) {
	//		if (g.E[i][j] != Maxint)
	//			printf("边为<%c,%c>,权值为%d:\n", g.V[i], g.V[j], g.E[i][j]);
	//	}
	//}
}

//找到顶点在顶点集中的标号
int LocateV(G_Amatreix g, VertexType v) {
	int j = 0;
	for (; j < g.n; ++j) {
		if (g.V[j] == v)
			return j;
	}
	return j;
}


/// ===============================================
/// Dijstra算法实现
/// ===============================================

bool S[MAX];           //记录顶点的访问情况,已访问的置为ture
int Path[MAX];         //记录源点到该顶点路径中的尾顶点的上一个顶点序号
int D[MAX];            //记录单源最短路径的路径长度


void Dijstra(G_Amatreix g,int v) {
	for (int i = 0; i < g.n; ++i) {        //用源点将各个辅助数据结构初始化
		S[i] = false;                     
		D[i] = g.E[v][i];                  
		if (D[i] < Maxint)
			Path[i] = v;
		else
			Path[v] = -1;
	}

	S[v] = true;                            //将源点的辅助数据结构置数
	D[v] = 0;

	for (int i = 1; i < g.n; ++i) {         //依次扫描下n-1个顶点
		int min = Maxint;
		for (int j = 0; j < g.n; ++j) {     //找出最小权值边加入顶点集合 S[]
			if (D[j] < min && !S[j]) {
				v = j;
				min = D[j];
			}
		}

		S[v] = true;
		
		for (int k = 0; k < g.n; ++k) {        //找出可以用该顶点所在路径可以更新剩余顶点的最短路径
			if (!S[k] && D[v] + g.E[v][k] < D[k]) {
				D[k] = D[v] + g.E[v][k];
				Path[k] = v;
			}
		}
		
	}

}

void Print(G_Amatreix g) {
	for (int i = 0; i < g.n; ++i) {     
		printf("S[%d]:%d\tPath[%d]:%d\tD[%d]:%d\n",i,S[i],i,Path[i],i,D[i]);
	}
}

/// ===============================================
/// Folyd算法实现
/// ===============================================

int Path1[MAX][MAX];         //记录源点到该顶点路径中的尾顶点的上一个顶点序号
int D1[MAX][MAX];            //记录最短路径的路径长度


void Floyd(G_Amatreix g) {
	for (int i = 0; i < g.n; ++i)          //用邻接矩阵将各个辅助数据结构初始化
		for (int j = 0; j < g.n; ++j) {    
			D1[i][j] = g.E[i][j];
			if (D1[i][j] < Maxint)
				Path1[i][j] = i;
			else
				Path1[i][j] = -1;
		}
		

	for (int k = 0; k < g.n; ++k)           //依次使用中间顶点对于最短路径的更新
		for (int i = 0; i < g.n; ++i)   
			for (int j = 0; j < g.n; ++j) {
				if ((D1[i][k] + D1[k][j] < D1[i][j]) && (i!=j)) {
					D1[i][j] = D1[i][k] + D1[k][j];
					Path1[i][j] = Path1[k][j];
				}
			}
}

void Print1(G_Amatreix g) {
	for (int i = 0; i < g.n; ++i)        
		for (int j = 0; j < g.n; ++j) {
			if(D1[i][j]!= Maxint)
				printf("D1[%d][%d]:%d\n",i,j,D1[i][j]);	
		}
}


int main(int argc, char* argv[]) {
	G_Amatreix g;
	CreateG(g);

	printf("\nDijstra:\n");
	Dijstra(g, 0);
	Print(g);

	printf("\nFolyd:\n");
	Floyd(g);
	Print1(g);


	return 0;
}

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Dijkstra算法是一种用于解决赋权的单源最短路径问题的算法。它使用类似广度优先搜索的方法,通过不断更新节点的距离来找到最短路径。以下是使用C/C++语言实现Dijkstra算法的示例代码: ```c #include <stdio.h> #include <limits.h> #define V 9 int minDistance(int dist[], bool sptSet[]) { int min = INT_MAX, min_index; for (int v = 0; v < V; v++) { if (sptSet[v] == false && dist[v] <= min) { min = dist[v]; min_index = v; } } return min_index; } void printSolution(int dist[]) { printf("Vertex \t Distance from Source\n"); for (int i = 0; i < V; i++) { printf("%d \t\t %d\n", i, dist[i]); } } void dijkstra(int graph[V][V], int src) { int dist[V]; bool sptSet[V]; for (int i = 0; i < V; i++) { dist[i] = INT_MAX; sptSet[i] = false; } dist[src] = 0; for (int count = 0; count < V - 1; count++) { int u = minDistance(dist, sptSet); sptSet[u] = true; for (int v = 0; v < V; v++) { if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) { dist[v] = dist[u] + graph[u][v]; } } } printSolution(dist); } int main() { int graph[V][V] = {{0, 4, 0, 0, 0, 0, 0, 8, 0}, {4, 0, 8, 0, 0, 0, 0, 11, 0}, {0, 8, 0, 7, 0, 4, 0, 0, 2}, {0, 0, 7, 0, 9, 14, 0, 0, 0}, {0, 0, 0, 9, 0, 10, 0, 0, 0}, {0, 0, 4, 14, 10, 0, 2, 0, 0}, {0, 0, 0, 0, 0, 2, 0, 1, 6}, {8, 11, 0, 0, 0, 0, 1, 0, 7}, {0, 0, 2, 0, 0, 0, 6, 7, 0}}; dijkstra(graph, 0); return 0; } ``` 这段代码实现了Dijkstra算法来找到从源节点到中所有其他节点的最短路径。它使用邻接矩阵来表示的结构,并使用数组来存储节点之间的距离。在主函数中,我们定义了一个9x9的,并将源节点设置为0。然后调用dijkstra函数来计算最短路径,并打印结果。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值