图的应用—求解最短路径(BFS、Dijkstra和Floyd算法)

BFS算法虽然可以求解最短路径问题,但是需要注意的是该算法只能求解非带权图的单源最短路径问题,或者说带权值相同且为1的图单源最短路径问题。

1、图的邻接矩阵存储结构定义

#define MaxVerNum 100            //顶点数目的最大值 
typedef struct{
	char Vex[MaxVerNum];     //顶点表
	int Edge[MaxVerNum][MaxVerNum];    //邻接矩阵,边表
	int vexnum,archnum;              //图的当前顶点数和弧数 
}Graph;

2、图的邻接表存储结构定义

#define MaxVerNum 100            //顶点数目的最大值 
typedef struct ArcNode{           //边表结点 
	int adjvex;                  //该弧所指向的顶点的位置
	struct ArcNode *next;        //指向下一条弧的指针 
	int num;                    //图的权值 
}ArcNode; 
typedef struct VNode{           //顶点表信息 
	char data;                  //顶点信息 
	ArcNode *first;              //指向第一条依附于该顶点的弧的指针 
}VNode,AdjList[MaxVerNum];
typedef struct{
	AdjList vertices;            //邻接表
	int vexnum,arcnum;             //图的顶点数和弧数 
}Graph;

3、BFS算法求解非带权图的单源最短路径

//BFS算法求解非带权图单源最短路径问题
void BFS_MIN_Distance(Graph G,int u)    //求到u的单源最短路径 
{
	for(int i=0;i<G.vexnum;i++)
	{
		d[i]=INF;         //初始化路径长度
		path[i]=-1;       //初始化到达每个顶点的前驱结点 
	}	
	visit[u]=true;
	d[u]=0;            //自身到自身的距离为0 
	EnQueue(Q,u);
	while(!isEmpty(Q))
	{
		DeQueue(Q,u);
		for(w=FirstNeighbor(G,u);w>=0;w=NextNeighbor(G,u,w))
		{
			if(!visit[w])
			{
				visit[w]=true;
				d[w]=d[u]+1;         //路径长度加1 
				path[w]=u;           //最短路径是从u到w 
				EnQueue(Q,w);       //w入队 
			}
		}
	}
} 

由于BFS算法的应用局限,所以对于带权值(正值)的图, 我们需要求解其单源最短路径的时候,就可以使用Dijkstra算法。

单源最短路径是指:图中某一顶点到其他各顶点的最短路径。

4、Dijkstra算法的定义 

Dijsktra算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层扩展,直到扩展到终点为止。在无向图G=(V,E)中,假设每条边E[i]的长度为w[i],找到由顶点v0到其余各点的最短路径。 

5、Dijkstra算法的基本思想 

1、把路径长度递增次序产生最短路径算法。

2、把V分为两组:

S:已求出最短路径的顶点的集合,初始时S中只有一个源点,以后每求得一条最短路径就将其加入到集合S中,直到所有顶点都加入到S中;

V-S=T:尚未确定最短路径的顶点集合;

3、将T中顶点按最短路径递增的次序加入到S中,保证:从源点V0到S中各顶点的最短路径长度都不大于从V0到T中任何顶点的最短路径长度。

4、每个顶点对应一个距离值:

S中顶点:从V0到此顶点的最短路径长度;

T中顶点:从V0到此顶点的只包括S中顶点作中间顶点的最短路径长度;

6、具体实现过程

首先将其全部初始化为无穷大(除了源点到源点之外):

在表中选择一个路径最短的点a,从a出发,a到自己的距离是0,它往外有三条边,更新a到b,c,d的最短距离,此时a被标记,则此后a都不会再被访问,此时该表更新为:

再在这张表中选择一个最短距离,是A->B,选出B作为新的起点,它有两条边分别是到达C,E,由于从B到C的距离是2,则从源点经过B到达C的最短距离是3,比之前的要小,所以更新最短距离,这时B也被标记,B不会再被访问。

再在这张表中选择一个最短距离,从AàC,则选择C点作为新的起点,从C出发有2条边,分别到达D,E,由于从A经过C到E的最短距离是23,小于之前的51,所以更新最短距离,从A经过C到达D的最短距离是8,小于之前的10,更新最短距离,则此时C被标记,C之后便不会再被访问。

再在这张表中选择一个最短距离,从A->D,所以选择D作为新的起点,从D只有一条边,到达E,则此时从A经过B,C,D到达E的最短距离是11,小于之前的23,所以更新最短距离,则此时D被标记,D不会再被访问。

再在这张表中选择一个最短距离,只有A->E,由于它并没有没访问的边,所以循环跳出,整个遍历结束!

所以这个有向图的单源最短路径是0,1,3,8,11。

 再需要更新最短路径的时候,使用数组tar记录下到达当前顶点的前一个顶点,也就是最佳路径的弧尾和弧头。然后使用深度优先遍历递归查找每个顶点到源点的最短路径。

7、给出Dijkstra算法求解单源最短路径的实例 

【问题描述】

给定一个n个顶点,m条有向边的带非负权图,请计算从s出发,到图中各点的距离。

【输入输出及示例】

输入:顶点数n,边数m,源点s,m条边以(p,q,weight)形式输入,分别表示弧尾、弧头和弧长;

输出:源点到每个点的距离,同时输出源点到每个点的路径。

示例1:

输入:顶点数:7,边数:10,源点:1

      弧尾、弧头、弧长为:

     1 2 13

      1 3 8

      1 5 30

      1 7 32

      2 6 9

      2 7 7

      3 4 5

      4 5 6

      5 6 2

      6 7 17

             

输出:源点到顶点1的最短路径长度是:0

      源点到顶点1的最短路径是:1

      源点到顶点2的最短路径长度是:13

      源点到顶点2的最短路径是:1 2

      源点到顶点3的最短路径长度是:8

      源点到顶点3的最短路径是:1 3

      源点到顶点4的最短路径长度是:13

      源点到顶点4的最短路径是:1 3 4

      源点到顶点5的最短路径长度是:19

      源点到顶点5的最短路径是:1 3 4 5

      源点到顶点6的最短路径长度是:21

      源点到顶点6的最短路径是:1 3 4 5 6

      源点到顶点7的最短路径长度是:20

      源点到顶点7的最短路径是:1 2 7

 代码:

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define INF 0X3F3F3F3F

//使用链式存储边,建图 
struct node{
	int begin;  //边的始点 
	int end;    //边的终点 
	int cost;   //边的代价 
	int next;   //当前边的下一条相邻边 
}e[1010]; 

int head[1010];   //head[i]存储与该点相邻的边
int dis[1010];    //存放源点到每个点的最短距离 
int visit[1010];  //标记哪些点被访问,访问的点被设为1 
int tar[1010];    //追踪最短路径

void dfs(int s,int v)   //输出最短路径 
{
	if(s==v)    //递归到源点,输出源点 
	{
		cout<<s<<" ";
		return ;
	}
	dfs(s,tar[v]);     //一直寻找到达该点的路径 
	cout<<v<<" ";
} 
int main()
{
	int n,m,s;
	cout<<"请输入顶点的个数、边的条数以及源点的位置: ";
	cin>>n>>m>>s;
	cout<<"请输入每条边的起点、始点和代价:"<<endl;
	for(int i=1;i<=m;i++)   //存储边,建图 
	{
		cin>>e[i].begin>>e[i].end>>e[i].cost;
		int from=e[i].begin;     //记录当前是从哪个点发出 
		e[i].next=head[from];    //存储该点的下一条边 
		head[from]=i;           // 更新与该点的相邻的最后一条边(也就是当前边) 
	} 
	
	for(int i=1;i<=n;i++)  //初始化,先将每个距离设置为最大值 
	{
		dis[i]=INF;
	} 
	
	int begin=s;
	dis[begin]=0;   //自己到自己的最短距离为0 
	
	while(visit[begin]!=1)  //对每个点进行查找,直至所有点被标记完 
	{
		visit[begin]=1;   //没被访问,那就访问 
		for(int i=head[begin];i!=0;i=e[i].next)  //依次访问与当前起点相邻的边
		{
			if(dis[e[i].end]>dis[e[i].begin]+e[i].cost)  //更新不同起点到达该点的最短距离 
			{
				dis[e[i].end]=dis[e[i].begin]+e[i].cost;
				tar[e[i].end]=e[i].begin;   //存储当前点的前一个点 
			}
		} 
		
		int min1=INF;
		for(int i=1;i<=n;i++)   //每一次都寻找所有还没被标记的点中距离最小的点
		{
			if(visit[i]!=1&&dis[i]<min1)
			{
				min1=dis[i];
				begin=i;   //以距离最小的点为新的起点,从当前点出发 
			}
		}
	} 
	
	cout<<"源点到每个点的最短路径长度:"<<endl;
	for(int i=1;i<=n;i++) 
	{
		cout<<"源点到顶点"<<i<<"的最短路径长度为:"<<dis[i]<<endl;
		if(dis[i]==INF)   //说明不可达
		{
			cout<<"该点不可达!"<<endl<<endl;
		} 
		else
		{
			cout<<"源点到顶点"<<i<<"的最短路径是:";
			dfs(s,i);
			cout<<endl<<endl;
		}
	}
	return 0;
}

需要注意的是:Dijkstra算法虽然能求解带权图的单源最短路径问题,但是其权值只能是非负值,对于负权值该算法不一定能得到正确结果。 

8、Floyd算法求各顶点之间最短路径问题 

Floyd算法其实是基于动态规划的思想,在原路径中尝试加入顶点k作为中间顶点,得到的新路径与原路径长度比较,得到最短路径。

#define Maxsize 100
int path[Maxsize][Maxsize];       //记录某一顶点到另一顶点的中转矩阵
int A[Maxsize][Maxsize];          //记录从顶点i到顶点j的路径长度 
//初始化准备工作,初始化矩阵A和path 
for(int k=0;k<n;k++)     //列举中转点 
{
	for(int i=0;i<n;i++)    //遍历整个矩阵的行和列 
	{
		for(int j=0;j<n;j++)
		{
			if(A[i][j]>A[i][k]+A[k][j])
			{
				A[i][j]=A[i][k]+A[k][j];    //更新最短路径 
				path[i][j]=k;              //记录中转点 
			}
		}
	}
}

Floyd算法的时间复杂度为O(|V|^3),空间复杂度为O(|V|^2)。该算法可以解决图中带负权值的问题,但是其不允许包含带负权值的边组成的回路,因为这种图有可能不存在最短路径。 

  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小阿丁呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值