最短路径四种方法

例题:HDU 2544

最短路
Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 89730    Accepted Submission(s): 38892


Problem Description
在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?


 

Input
输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。
输入保证至少存在1条商店到赛场的路线。

 

Output
对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间
 

Sample Input
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
 

Sample Output
3
2
 

1),深度或广度优先搜索算法(解决单源最短路径)
从起始结点开始访问所有的深度遍历路径或广度优先路径,则到达终点结点的路径有多条,取其中路径权值最短的一条则为最短路径。
给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为
源。
现在要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通
常称为单源最短路径 [1] 问题。
从起始结点开始访问所有的深度遍历路径或广度优先路径,则到达终点结点的路径有多条,取其中路
径权值最短的一条则为最短路径

下面是核心代码:

//题意:求1->n的最短路径
#include<iostream>
#include<string.h>
#define inf 99999999
using namespace std;
int dis[111][111];
bool vis[111];
int n,cnt;//n为节点数,cnt为最短长度
void init(int x){
         for(int i=0;i<=n;i++){
                  for(int j=0;j<=n;j++)
                           dis[i][j]=inf;
                  dis[i][i]=0;
                  vis[i]=0;
         }
}
void dfs(int st,int dst)
{
         if(dst>cnt)return ;//距离大于最短路径,无需遍历
         if(st==n){//到达终点
                  cnt=cnt>dst?dst:cnt;
                  return;
         }
         for(int i=1;i<=n;i++)
         {
                  if(!vis[i]&&dis[st][i]!=inf&&dis[st][i]){
                           vis[i]=1;
                           dfs(i,dst+dis[st][i]);
                           vis[i]=0;
                  }
         }
}
int main()
{
         int m;
         while(~scanf("%d%d",&n,&m)&&n&&m)
         {
                  int x,y,len;
                  cnt=inf;
                  init(n);
                  while(m--){
                           scanf("%d%d%d",&x,&y,&len);
                           dis[x][y]=min(dis[x][y],len);//两点之间距离重复输入取小距离
                           dis[y][x]=dis[x][y];
                  }
                  vis[1]=1;
                  dfs(1,0);
                  printf("%d\n",cnt);
         }
         return 0;
}
Sample Input 2
5 14
2 2 262
5 3 403
4 2 456
1 5 289
3 1 1000
2 4 217
2 5 536
2 5 415
2 4 880
3 1 179
3 4 972
5 3 2
1 3 491
4 1 872
0 0
Sample Output 2
181

2),弗洛伊德算法(解决多源最短路径):时间复杂度O(n^3),空间复杂度O(n^2)
基本思想:最开始只允许经过1号顶点进行中转,接下来只允许经过1号和2号顶点进行中转......允许经过1~n号所有顶点进行中转,来不断动态更新任意两点之间的最短路程。即求从i号顶点到j号顶点只经过前k号点的最短路程。

//题意:求1->n的最短路径
#include<iostream>
#include<string.h>
#define inf 99999999
using namespace std;
int n,dis[111][111];
void init(){
         for(int i=0;i<=n;i++){
                  for(int j=0;j<=n;j++)
                           dis[i][j]=inf;
                  dis[i][i]=0;
         }
}
int main()
{
         int m;
         while(~scanf("%d%d",&n,&m)&&n&&m)
         {
                  init();
                  while(m--){
                           int x,y,len;
                           scanf("%d%d%d",&x,&y,&len);
                           dis[x][y]=min(dis[x][y],len);
                           dis[y][x]=dis[x][y];
                  }
                  for(int k=1;k<=n;k++)//要经过的点
                           for(int i=1;i<=n;i++)
                                    for(int j=1;j<=n;j++)
                                             dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
                  printf("%d\n",dis[1][n]);//可以选任意两点之间的距离
         }
         return 0;
}
Sample Input 2
5 14
2 2 262
5 3 403
4 2 456
1 5 289
3 1 1000
2 4 217
2 5 536
2 5 415
2 4 880
3 1 179
3 4 972
5 3 2
1 3 491
4 1 872
0 0
Sample Output 2
181

3),迪杰斯特拉算法(解决单源最短路径)
基本思想:每次找到离源点(如1号结点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。

基本步骤:1.开容器v,储存子节点、距离、花费;2、开数组dis记录起始点到各点距离;3、进行n-1次松弛操作(先找出未标记点中离起始点最近的点,标记该点,然后求出该点子节点到起始点的最短距离(优先)与最短花费);4、输出到终点的最短距离与花费;

//题意:求两点之间最短路径
#include<iostream>
#include<string.h>
#include<vector>
#include<algorithm>
#define N 999999999
using namespace std;
struct node{
         int er,len,cost;
};
vector<node>v[1111];
int main()
{
         int n,m;
         while(~scanf("%d%d",&n,&m)&&n&&m)
         {
                  int dis[1111],spend[1111];
                  bool vis[1111];
                  node tmp;
                  int x,y;
                  for(int i=0;i<1111;i++)
                           v[i].clear();
                  while(m--){
                           scanf("%d%d%d%d",&x,&y,&tmp.len,&tmp.cost);
                           tmp.er=x;
                           v[y].push_back(tmp);
                           tmp.er=y;
                           v[x].push_back(tmp);
                  }
                  scanf("%d%d",&x,&y);//起点和终点
                  for(int i=1;i<=n;i++){
                           vis[i]=0;
                           dis[i]=spend[i]=N;
                  }
                  for(int i=0;i<v[x].size();i++){
                           dis[v[x][i].er]=v[x][i].len;
                           spend[v[x][i].er]=v[x][i].cost;
                  }
                  vis[x]=1;
                  for(int k=1;k<=n-1;k++)
                  {
                           int id,mi=N;
                           for(int i=1;i<=n;i++){
                                    if(!vis[i]&&dis[i]<mi){//查询并记录离x最近的点
                                             id=i;mi=dis[i];
                                    }
                           }
                           vis[id]=1;//标记过的点已经是最短
                           for(int i=0;i<v[id].size();i++)
                           {
                                    int vv=v[id][i].er;
                                    if(!vis[vv]&&dis[vv]>dis[id]+v[id][i].len)//未标记、直接距离大于通过id点的距离
                                             dis[vv]=dis[id]+v[id][i].len,
                                             spend[vv]=spend[id]+v[id][i].cost;
                                    else if(!vis[vv]&&dis[vv]==dis[id]+v[id][i].len&&spend[vv]>spend[vv]+v[id][i].cost)//未标记、距离相等找花费更小的
                                             spend[vv]=spend[id]+v[id][i].cost;
                           }
                  }
                  printf("%d %d\n",dis[y],spend[y]);
         }
         return 0;
}
/*
3 2
1 2 5 6
2 3 4 5
1 3
3 2
1 3 5 6
2 1 3 5
3 2

9 11
8 11
*/

4),Bellman-Ford算法(解决负权边,解决单源最短路径,前几种方法不能求含负权边的图)::时间复杂度O(nm),空间复杂度O(m)
主要思想:对所有的边进行n-1轮松弛操作,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1边。换句话说,第1轮在对所有的边进行松弛后,得到的是从1号顶点只能经过一条边到达其余各定点的最短路径长度。第2轮在对所有的边进行松弛后,得到的是从1号顶点只能经过两条边到达其余各定点的最短路径长度,......
以下是图示:

此外,Bellman_Ford还可以检测一个图是否含有负权回路:POJ1860

/*
题意:有多种汇币,汇币之间可以交换,这需要手续费,当你用100A币
交换B币时,A到B的汇率是29.75,手续费是0.39,那么你可以得到
(100 - 0.39) * 29.75 = 2963.3975 B币。问s币的金额经过交换最终
得到的s币金额数能否增加
货币的交换是可以重复多次的,所以我们需要找出是否存在
正权回路,且最后得到的s金额是增加的
怎么找正权回路呢?(正权回路:在这一回路上,顶点的权值能不断增加即能一直进行松弛)
分析:
反向利用Bellman-Ford算法
单源最短路径算法,因为题目可能存在负边,所以用Bellman Ford算法,
原始Bellman Ford可以用来求负环,这题需要改进一下用来求正环

一种货币就是图上的一个点
一个“兑换点”就是图上两种货币之间的一个兑换环,相当于“兑换方式”M的个数,是双边
唯一值得注意的是权值,当拥有货币A的数量为V时,A到A的权值为K,即没有兑换
而A到B的权值为(V-Cab)*Rab
本题是“求最大路径”,之所以被归类为“求最小路径”是因为本题题恰恰
与bellman-Ford算法的松弛条件相反,求的是能无限松弛的最大正权路径,
但是依然能够利用bellman-Ford的思想去解题。
因此初始化d(S)=V   而源点到其他店的距离(权值)初始化为无穷小(0),
当s到其他某点的距离能不断变大时,说明存在最大路径
*/
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
struct node
{
         int x,y;
         double r,c;
}num[222];
int n,m,s,ans;
double v;
void add(int x,int y,double r,double c)
{
         num[ans].x=x;
         num[ans].y=y;
         num[ans].r=r;
         num[ans].c=c;
         ans++;
}
bool bellon()
{
         double dis[111];
         for(int i=0;i<=n;i++)
                  dis[i]=0;
         dis[s]=v;
         for(int j=1;j<n;j++)
         {
                  bool flag=0;
                  for(int i=0;i<ans;i++){
                           if(dis[num[i].y]<(dis[num[i].x]-num[i].c)*num[i].r)
                                    dis[num[i].y]=(dis[num[i].x]-num[i].c)*num[i].r,flag=1;
                  }
                  if(!flag)
                           return 0;
         }
         for(int i=0;i<ans;i++)
                  if(dis[num[i].y]<(dis[num[i].x]-num[i].c)*num[i].r)
                           return 1;
         return 0;
}
int main()
{
         int a,b;
         ans=0;
         double ra,rb,ca,cb;
         scanf("%d%d%d%lf",&n,&m,&s,&v);
         while(m--)
         {
                  scanf("%d%d%lf%lf%lf%lf",&a,&b,&ra,&ca,&rb,&cb);
                  add(a,b,ra,ca);
                  add(b,a,rb,cb);
         }
         if(bellon())
                  printf("YES\n");
         else
                  printf("NO\n");
         return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值