图的最短路径算法分析与总结

                                       图的求最短路径算法大类可以分为4种,在这里一一介绍

1.Floy算法

2.Dijkstra算法

3.Bellman-Ford算法

4.Bellman-Ford算法的队列优化



注:以下程序是以编号为1的节点为源点的程序,如果需要更改,可以自行更改。
一, Floy算法
基本思想:
1.数据结构:邻接矩阵
2.算法思想:动态规划
核心代码:

	for(k=1;k<=n;k++)
	{
		for(i=1;i<=n;i++)
		{
			for(j=1;j<=n;j++)
			{
				if(e[i][k]+e[k][j]<e[i][j])
				{
					e[i][j]=e[i][k]+e[k][j];
				}
			}
		}
	}

e[i][j]是表示从i节点到j节点的距离
如果I=2,j=7;
其前面的已经是最优解,只需判断一下经过哪个节点最小。

注:邻接矩阵的建立:
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
		{
			if(i==j)  e[i][j]=0;
			else      e[i][j]=999999;
 		}
 	for(i=1;i<=m;i++)
 	{
 		scanf("%d %d %d",&x,&y,&v);
 		e[x][y]=v;
	}






二.   Dijkstra算法
数据结构:邻接矩阵
算法思想:基于贪心算法



核心代码:


	for(i=1;i<n-1;i++)
	{
		min=inf;
		for(j=1;j<=n;j++)
		{
			if(book[j]==0&&dis[j]<min)
			{
				min=dis[j];
				u=j;
			}
		}
		book[u]=1;
		for(v=1;v<=n;v++)
		{
			if(e[u][v]<inf)
			{
				if(dis[v]>dis[u]+e[u][v])
				{
					dis[v]=dis[u]+e[u][v];
				}
			}
		}
	}

这个算法不能解决负边权问题

主要思想:
1.找到当前里源点最近的点,然后把其值改为确定值    
原因:因为再经过任何一边都会增加其值,所以其值一定最小,这也是为什么其不能解决负边权问题的原因。
2.用选择法更新每个点,如果某个点经过这个点路径会减小,则更新那个点,否则则不更新
3.不断重复1.2步骤,直到其所有点都已经成为标记值




三,bellman- ford算法
数据结构:数组
算法思想:遍历利用边 ”松弛“


核心代码:

	for(k=1;k<=n-1;k++)
	{
		for(i=1;i<=m;i++)
		{
			if(dis[v[i]]>dis[u[i]]+w[i])
				dis[v[i]]=dis[u[i]]+w[i];
		}
	}

该算法是以边为主要思想的遍历每个边,每一个边选择或者不选择,每个选择的边都进行了一遍 “松弛”
每松弛第i次就代表着从出发点开始,经过i条边所能达到的最小距离,所以在没有环的情况下,只需走n-1次
所以有n-1次循环外层
如果有环,如果是负边权的,则其没有最小值,如果是正边权,这其不必经过环,
那下面讨论一下怎么确定其是否有负边权的环:当循环n-1次后,如果继续循环其还变化的话,就说明有负边权的环,否则其没有负边权的环
代码如下:


	int flag=0;
	for(i=1;i<=m;i++)
		if(dis[v[i]]>dis[u[i]]+w[i])  flag=1;
	if(flag)  printf("there are loop in the map!!");

四,bellman-ford算法的队列优化

基本思想:利用队列实现了bellman-ford算法的核心思想,就是代码长度增加,代码理解起来相对难,但是减少了时间复杂度,减少了bellman-ford里面不必要的循环。
数据结构:队列,数组
个人理解:其有点像广度优先搜索,和动态规划
由于会比较难理解,就上全部代码:

#include<stdio.h>
#include<string.h>
#include<conio.h> 
int main()
{
	int n,m,i,j,k;
	int u[8],v[8],w[8],e[100][100]={0};
	int first[6],next[8];
	int dis[6]={0},book[6]={0};
	int que[101]={0},head=1,tail=1;
	int inf	=99999;
	scanf("%d %d",&n,&m);
	for(i=1;i<=n;i++)	dis[i]=inf;
	dis[1]=0;
	memset(book,0,sizeof(book));
	for(i=1;i<=n;i++)	first[i]=-1;
	for(i=1;i<=m;i++)
	{
		scanf("%d %d %d",&u[i],&v[i],&w[i]);
		e[u[i]][v[i]]=w[i];
		//very beautiful solution
		//very important ! !  //就是一条边指向另一条边 
		next[i]=first[u[i]];//判断是否有与第i条边有相同节点的边,有的话就把该边赋给next[i] 
		first[u[i]]=i;//把边的序号赋值给first的第节点个个的元素 
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=n;j++)
		{
			printf("%5d",e[i][j]);
		}
		printf("\n");
	}
		
	printf("\n");
	for(i=1;i<=m;i++)  printf("%d ",first[i]);
	printf("\n");
	for(i=1;i<=m;i++)  printf("%d ",next[i]);
	printf("\n");
	que[tail]=1;  tail++;
	book[1]=1;
	while(head<tail)
	{
		k=first[que[head]];
		while(k!=-1)
		{
			if(dis[v[k]]>dis[u[k]]+w[k])//k代表第几条边 
			{
				dis[v[k]]=dis[u[k]]+w[k];
				if(book[v[k]]==0)
				{
					que[tail]=v[k];
					tail++;
					book[v[k]]=1;
				}
			}
			k=next[k];
		}
		book[que[head]]=0;
		head++;
	}
	for(i=1;i<=n;i++)	printf("%5d",dis[i]);
	getch();
	return 0; 
} 


代码分析:
1.邻接表的两个数组储存:

	for(i=1;i<=m;i++)
	{
		scanf("%d %d %d",&u[i],&v[i],&w[i]);
		e[u[i]][v[i]]=w[i];
		//very beautiful solution
		//very important ! !  //就是一条边指向另一条边 
		next[i]=first[u[i]];//判断是否有与第i条边有相同节点的边,有的话就把该边赋给next[i] 
		first[u[i]]=i;//把边的序号赋值给first的第节点个个的元素 
	}


先大概分析一下代码:
首先first数组里面存的数据会被覆盖,所以其存储的数据必定是最后一个数据,因为其前面的数据都被覆盖掉了。
然后next数组里面储存的数据不会被覆盖,而且其储存的肯定是与第i个边有相同头结点的边的编号,而且其比i小且为最临近的一条边,因为再前面的边的编号已经被覆盖。


来看下实例,就会进一步理解!
先来数据:

测试数据:

5 7          //5个顶点,7条边

1 2 2

1 5 10

2 3 3

2 5 7

3 4 4

4 5 5

5 3 6


first[]2  4  5  6  7  7  5
next[]
-1 1 -1 3 -1 -1 1
这就是其运行结果。
可以分析出:first[]数组第i个元素代表编号为i点所在的最后一个边的编号
                      next[]第i个元素代表与第i个边有相同前结点的 由大到小离i边编号最近的 一个边的 编号





可以看出,其用两个数组存储邻接表,然后就是如何利用这个数据结构了
来看代码:

while(head<tail)
	{
		k=first[que[head]];
		while(k!=-1)
		{
			if(dis[v[k]]>dis[u[k]]+w[k])//k代表第几条边 
			{
				dis[v[k]]=dis[u[k]]+w[k];//同bellman-ford算法思想
				if(book[v[k]]==0)
				{
					que[tail]=v[k];//入队
					tail++;
					book[v[k]]=1;//标记
				}
			}
			k=next[k];//利用其找到有相同前节点的边的编号
		}
		book[que[head]]=0;//出队,并标记
		head++;
	}




因为这个算法用队列实现的,所以从源点开始,每遇到一个“新‘’节点入队,同时处理入队的节点,每处理完一个节点,该节点出队。

这里的”新”是指”队列“中不存在的节点

实现方法:用book[]数组标记



当队列为空时,代表着其已经处理完所有的数据,结束计算,输出。


到此四个计算最短路径的算法已经介绍完,其中应用的最主要的思想就是动态规划,即当前解为最优解。

然后就是Dijkstra算法中应用的贪心算法(导致其只能用于没有负边权的图)
Floy和Dijkstra算法中是以节点为中心,运用邻接矩阵
Bellman-ford  和Bellman-ford的队列优化  是以边为中心并继承Dijkstra的dis[]一维数组


先写这些了,,以后有发现再补上!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值