例题: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;
}