数据结构(最短路径)

本人还尚未系统性学习JAVA,故此次笔记学习数据结构C语言版本中的图的最短路径问题。

路径

考虑带权有向图,把一条路劲(仅仅考虑简单路径)上所经边的权值之和定义为该路径的路径长度或称带权路径长度

 从源点到终点可能不止一条路径,把路径长度最短的那条路径称为最短路径。很多情况下,两个顶点的最短路径不一定唯一,但最短路径长度一定是唯一的

最短路径算法

狄克斯特拉(Dijkstra)算法

问题:给定一个带权有向图G与源点v,求从v到G中其他顶点的最短路径,并限定各边上的权值大于或等于0。

单源最短路径问题可以通过 Dijkstra算法 解决

基本思路

设 G=(V,E) 是一个带权有向图,把图中顶点集合V分成两组:

——第一组已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径v,……,u,就将u加入到集合S中,直到全部顶点都加入到S中,算法结束)

——第二组其余未求出最短路径的顶点集合(用U表示),按最短路径长度的递增次序把第二组的顶点加入到S中

 

具体步骤

(1)初始化:S只包含源点即S={v},v 的最短路径为0。U包含除 v 外的其他顶点,U中顶点 i 距离为边上的权值(若v与i有边 < v,i > )或无穷(若i不是v的出边邻接点)。

 (2)从U中选取一个距离 v 最小的顶点 u ,把 u 加入到S中(该选定的距离就是 v 到 u 的最短路径长度)。

(3)以 u 为新考虑的中间点,修改U中各顶点 j 的最短路径长度:若从源点 v 到 j( j 属于U)的最短路径长度(经过顶点 u )比原来最短路径长度(不经过顶点 u )短,则修改顶点 j 的最短路径长度。

 (4)重复(2)和(3)直到所有顶点都包含在S中。

 

 

算法设计

如何存放最短路径长度:

        用一维数组dist[ j ]存储。源点 v 默认,dist[ j ]表示源点到顶点 j 的最短路径长度。

如何存放最短路径:

        从源点到其他顶点的最短路径有 n-1 条,一条最短路径用一个一维数组表示,所有 n-1 条最短路径可以用二维数组path[ ][ ]存储。

对应的Dijkstra算法如下:

void Dijkstra(MatGraph g,int v)	//Dijkstra算法
{	int dist[MAXV],path[MAXV];
	int S[MAXV];				//S[i]=1表示顶点i在S中, S[i]=0表示顶点i在U中
	int Mindis,i,j,u;
	bool flag;
	for (i=0;i<g.n;i++)
	{	dist[i]=g.edges[v][i];	//距离初始化
		S[i]=0;					//S[]置空
		if (g.edges[v][i]<INF)	//路径初始化
			path[i]=v;			//顶点v到顶点i有边时,置顶点i的前一个顶点为v
		else
			path[i]=-1;			//顶点v到顶点i没边时,置顶点i的前一个顶点为-1
	}
	disp(dist,path,g.n);
	printf("(%d)将顶点%d添加到S集合\n",++count,v);
	S[v]=1;path[v]=0;			//源点编号v放入S中
	for (i=0;i<g.n-1;i++)		//循环直到所有顶点的最短路径都求出
	{	Mindis=INF;				//Mindis置最大长度初值
		for (j=0;j<g.n;j++)		//选取不在S中(即U中)且具有最小最短路径长度的顶点u
			if (S[j]==0 && dist[j]<Mindis) 
			{	u=j;
				Mindis=dist[j];
			}
		printf("  求出U中最小的顶点%d\n",u);
		printf("(%d)将顶点%d添加到S集合\n",++count,u);
		S[u]=1;					//顶点u加入S中
		flag=false;
		for (j=0;j<g.n;j++)		//修改不在S中(即U中)的顶点的最短路径
			if (S[j]==0)
			{
				if (g.edges[u][j]<INF)
				{
					flag=true;
					printf("  考虑顶点%d的邻接点%d:",u,j);
					if (dist[u]+g.edges[u][j]<dist[j])
					{
						dist[j]=dist[u]+g.edges[u][j];
						printf("修改其最短路径长度dist[%d]为%d,",j,dist[j]);
						path[j]=u;
						printf("修改最短路径path[%d]为%d\n",j,u);
					}
					else
						printf("顶点%d的最短路径长度没有修改\n",j);
				}
			}
		if (!flag)
			printf("   顶点%d没有未考虑的邻接点(不修改)\n",u);
		disp(dist,path,g.n);
	}
	Dispath(g,dist,path,S,v);	//输出最短路径
}

输出单源最短路径的Dispath()函数如下:

void Dispath(MatGraph g,int dist[],int path[],int S[],int v)
//输出从顶点v出发的所有最短路径
{	int i,j,k;
	int apath[MAXV],d;					//存放一条最短路径(逆向)及其顶点个数
	for (i=0;i<g.n;i++)					//循环输出从顶点v到i的路径
		if (S[i]==1 && i!=v)
		{	printf("  从顶点%d到顶点%d的路径长度为:%d\t路径为:",v,i,dist[i]);
			d=0; apath[d]=i;			//添加路径上的终点
			k=path[i];
			if (k==-1)					//没有路径的情况
				printf("无路径\n");
			else						//存在路径时输出该路径
			{	while (k!=v)
				{	d++; apath[d]=k;
					k=path[k];
				}
				d++; apath[d]=v;		//添加路径上的起点
				printf("%d",apath[d]);	//先输出起点
				for (j=d-1;j>=0;j--)	//再输出其他顶点
					printf(",%d",apath[j]);
				printf("\n");
			}
		}
}

不考虑路径的输出,Dijkstra算法的时间复杂度为O(n^2),其中n图中顶点的个数

弗洛伊德(Floyd)算法

问题:对于一个各边权值大于0的有向图,对每一个顶点 i 不等于 j,求出顶点 i 与顶点 j 之间的最短路径和最短路径长度。

多源最短路径问题可以通过 Floyd 算法 解决

算法思路

假设有向图G=( V , E )采用邻接矩阵存储。设置一个二维数组 A 用于存放当前顶点之间的最短路径长度,分量 A[ i ][ j ]表示当前顶点 i 到 j 的最短路径长度。

 递推产生一个矩阵序列:

A[ i ][ j ]:i 到 j 的路径上所经过的顶点编号不大于 k 的最短路径长度

 

算法设计

用二维数组 A 存储最短路径长度:

        A[ i ][ j ]表示考虑顶点0 ~ k 后得出的 i 到 j 的最短路径长度。

        数组最后一个A[ i ][ j ] 表示最终的 i 到 j 的最短路径长度。

用二维数组path存放最短路径:

        path[ i ][ j ]表示考虑顶点0 ~ k 后得出的 i 到 j 的最短路径。

        数组最后一个path[ i ][ j ]表示最终 i 到 j 的最短路径。

 对应的Floyd算法如下:

void Floyd(MatGraph g)							//Floyd算法
{	int A[MAXV][MAXV],path[MAXV][MAXV];
	int i,j,k;
	for (i=0;i<g.n;i++)
		for (j=0;j<g.n;j++) 
		{	A[i][j]=g.edges[i][j];
			if (i!=j && g.edges[i][j]<INF)
				path[i][j]=i;					//顶点i到j有边时
			else
				path[i][j]=-1;				//顶点i到j没有边时
		}
	for (k=0;k<g.n;k++)						//依次考察所有顶点
	{	for (i=0;i<g.n;i++)
			for (j=0;j<g.n;j++)
				if (A[i][j]>A[i][k]+A[k][j])
				{	A[i][j]=A[i][k]+A[k][j];	//修改最短路径长度
					path[i][j]=path[k][j];		//修改最短路径
				}
	}
	Dispath(g,A,path);							//输出最短路径
}

输出多源最短路径的Dispath()函数如下:

void Dispath(MatGraph g,int A[][MAXV],int path[][MAXV])
{	int i,j,k,s;
	int apath[MAXV],d;		//存放一条最短路径中间顶点(反向)及其顶点个数
	for (i=0;i<g.n;i++)
		for (j=0;j<g.n;j++)
		{	if (A[i][j]!=INF && i!=j)			//若顶点i和j之间存在路径
			{	printf("  从%d到%d的路径为:",i,j);
				k=path[i][j];
				d=0; apath[d]=j;				//路径上添加终点
				while (k!=-1 && k!=i)			//路径上添加中间点
				{	d++; apath[d]=k;
					k=path[i][k];
				}
				d++; apath[d]=i;				//路径上添加起点
				printf("%d",apath[d]);			//输出起点
				for (s=d-1;s>=0;s--)			//输出路径上的中间顶点
					printf(",%d",apath[s]);
				printf("\t路径长度为:%d\n",A[i][j]);
			}
		}
}

不考虑路径输出,Floyd算法的时间复杂度为O(n^3),其中 n 为图中的顶点的个数。

比较

算法用途时间复杂度特点
Dijkstra单源最短路径

O(n^2)

不适合负权
Floyd多源最短路径O(n^3)不适合负权回路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值