算法训练 最短路

问题描述

给定一个n个顶点,m条边的有向图(其中某些边权可能为负,但保证没有负环)。请你计算从1号点到其他点的最短路(顶点从1到n编号)。

输入格式

第一行两个整数n, m。

接下来的m行,每行有三个整数u, v, l,表示u到v有一条长度为l的边。

输出格式
共n-1行,第i行表示1号点到i+1号点的最短路。
样例输入
3 3
1 2 -1
2 3 -1
3 1 2
样例输出
-1
-2
数据规模与约定

对于10%的数据,n = 2,m = 2。

对于30%的数据,n <= 5,m <= 10。

对于100%的数据,1 <= n <= 20000,1 <= m <= 200000,-10000 <= l <= 10000,保证从任意顶点都能到达其他所有顶点。


哈哈,又是这道题,这次使用更加简易的Bellman-Ford算法,思路也简单,

就是不断地进行松弛操作,而在一个N个的图中,任意两个点之间的最短路径

最多可以包含n-1条边,也就是说,每条边的松弛操作最多要进行n-1次,

对,每条边,也就是说时间复杂度为O(mn),看起来比Dijkstra算法还要不靠谱,

但是,由于大部分情况我们不必进行N-1次循环就可以得到最短路,所以我们可以

用个flag标记,如果在一轮松弛操作中没有可以松弛的,说明所有的路径都是最短路,这时候就可以退出循环。

下面是  只有4行代码 的 Bellman-Ford算法,

只有4行,简直是是我这样记忆力差的人福音啊

for(int i=1;i<=n-1;i++)       //外层  循环 n-1次
	for(int j=1;j<=m;j++)    //每条边
	     if(dis[v[j]] > dis[u[j]] + w[j]){   //松弛操作,如果1号顶点到v[j]的距离大于 u[j]的距离加上 u[j]->v[j] 这条边的距离
		 dis[v[j]] = dis[u[j]] + w[j];
	}


下面是AC代码,时间为500ms,还算可以

#include<iostream>
#define MAX_NUM 200010
using namespace std;
int dis[MAX_NUM];
int u[MAX_NUM];
int v[MAX_NUM];
int w[MAX_NUM];
int n,m;
const int inf =9999999;
int main()
{
	freopen("2.txt","r",stdin);
	freopen("22.txt","w+",stdout);
	cin>>n>>m;
	//初始化
	for(int i=1;i<=n;i++)
		dis[i]=inf;
	for(int i=1;i<=m;i++)
	{
		cin>>u[i]>>v[i]>>w[i];
	}
	dis[1]=0;	
	//bellman 算法核心 
	for(int i=1;i<=n-1;i++){
		int flag=0;  //标记 
		for(int j=1;j<=m;j++){
			if(dis[v[j]] > dis[u[j]] + w[j]){
				dis[v[j]] = dis[u[j]] + w[j];
				flag=1;	 
			}	
		}
		if(flag==0) break;  
		//如果这一轮没有一条边松弛,说明所有的路径都是最短的,退出循环 
	}
	for(int i=2;i<=n;i++)
		printf("%d\n",dis[i]);

	return 0;
 } 


//更新,下面是Bellman算法的队列优化,将经过松弛操作的顶点入队,并将队列中的顶点全部进行松弛操作,如果队列中已经存在此顶点便不重复入队,用book数组来标记。这样可以避免无谓的对一些顶点进行松弛操作,达到优化的效果。
使用邻接表的形式存储边。。。

#include<iostream>
#define MAX_NUM 200010
#define MAX 20010
using namespace std;
int dis[MAX_NUM];		//1号顶点到个顶点的距离 
int u[MAX_NUM];			//左顶点 
int v[MAX_NUM];			//右顶点 
int w[MAX_NUM];			//权重 
int first[MAX_NUM];		//邻接表  
int next[MAX_NUM];
int que[MAX_NUM];		//队列 
int tail=1,head=1;		//队首 队尾 
int book[MAX_NUM];		//标记数组    
int n,m;				//顶点数  边数 
const int inf = 999999;
int main()
{
	freopen("2.txt","r",stdin);
	cin>>n>>m;
	//初始化
	for(int i=1;i<=n;i++)
		first[i]=-1;
	for(int i=1;i<=n;i++)
		dis[i] = inf;
	dis[1]=0;			//1号顶点到自身的距离	 
	
	for(int i=1;i<=m;i++)
	{
		cin>>u[i]>>v[i]>>w[i];
		next[i] = first[u[i]];		//first[u[i]]保存 u[i]顶点 的第一条边 
		first[u[i]] = i;			//next[i] 保存编号为 i 的 下一条边 
									//邻接表的边是插入表头而不是表尾 
 	} 
 	que[tail++]=1; 					//1号顶点入队
	book[1]=1;
	while(head<tail){				//队不为空 
		int k=first[que[head]];		//取队头元素	
		while(k!=-1){
			if(dis[v[k]] > dis[u[k]] + w[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(int i=2;i<=n;i++)
 		cout<<dis[i]<<endl;
	return 0;
 } 




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值