图论:HDU2544-最短路(最全、最经典的最短路入门及小结)

时间:2022-06-19 14:55:54

最短路

Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

Total Submission(s): 45288    Accepted Submission(s): 19993

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
 

Source
 

Recommend
lcy   |   We have carefully selected several similar problems for you:  2066 1874 1217 2112 1142 


图论:HDU2544-最短路(最全、最经典的最短路入门及小结)


解题心得:
1、代码主要是写写最短路的模板,包括dij(n^2,和nlogn),Floyd,spfa,包括一些优化。还是看代码吧,代码上有注释。

2、下面说说几种计算最小路径的方法的优劣。

关于dij算法主要就是一个松弛的方法外加上一个贪心的思想,它的实现原理原理可以看成三角形两边之和大于第三边,它是从起点找到距离它最近的一个点,那么这个当前最短最短边一定是最短距离了,因为如果从其他的边去走会出线至少两条比最短边长的边之和,和上图的展示效果相同,也正是这个特性dij算法不能够计算带有复权边的图,带有复权边只有就不符合三角形定理了,两边之和会小于第三遍边,只不过使用代码实现上面的图的过程。然而就经典的dij算法占用的时间和空间的复杂多过于大了,所有才有了后面的使用优先队列和堆优化。而在空间方面的优化也可以使用vector动态数组,但是如果出题人卡你的stl就可以使用邻接表关于vector和邻接表可以看看后面的代码。其实说白了dij算法就是一个A*算法,有空的可以去看看A*算法。(dij的优化有很多,不懂得可以看看代码,代码看不懂的去网上看看详解)。


而floyd算法是在面对非单源最短路的情况下使用,因为使用floyd算法可以得到任意两个点之间的最短路径。Floyd的原理其实是一个动态规划,例如从a到b的距离可以在a到b之间找一个点从,看是否从a到c再从c到b的距离小于直接从a到达b。如果是则将a到b的更短的距离替换成当前距离逐步得到最短的距离。但是Floyd的复杂度是O(n^3)在一般情况下都会超时,但是floyd可以在一些情况下对数据进行处理(例如,与处理中),在每次使用floyd算法的时候最好都先计算一下复杂度再使用。


spfa算法可以看成一个bfs,从起点开始搜索,然后层层松弛,spfa时间复杂度是O(VE,但是如果图中存在复权的环那么会一直压入,不停地找到更小的值,所以在这个时候可以判断一下,如果一条边压入次数超过了n次,也就是节点的次数,这时可以直接判断不可能因为有复权环。但是就spfa算法来说是可以计算带有复权边的图的,但是不能带有复权环。


普通的dij    n^2的复杂度
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100+10;
const int Max = 99999999;
int d[1000010];
int maps[maxn][maxn];
int n,m;
bool use[maxn];//用于标记,避免循环走 void Ds()
{
while(1)
{
int v = -1;//记录最近的点
for(int i=1;i<=n;i++)
if((v == -1 || d[i]<d[v]) && !use[i])
v = i;
if(v == -1)
break;
use[v] = 1;
for(int i=1;i<=n;i++)
d[i] = min(d[i],d[v]+maps[i][v]);//d[v] + maps[i][v],记录从起点到i的距离
}
}
int main()
{
//printf("%d",Max);
while(scanf("%d%d",&n,&m) && n+m)
{
for(int i=0;i<maxn;i++)
for(int j=0;j<maxn;j++)
maps[i][j] = Max;//初始化为最大
memset(use,0,sizeof(use));
for(int i=0;i<=m*n+10;i++)
d[i] = Max;//初始化为最大
for(int i=0;i<m;i++)
{
int start,end;
int len;
d[1] = 0;//自身走到自身是0
scanf("%d%d%d",&start,&end,&len);
maps[start][end] = maps[end][start] = len;//是双向的并没有固定方向
}
Ds();
printf("%d\n",d[n]);
}
}


dij算法优先队列的优化和空间的处理
/*
这个主要优化的是空间存放问题和时间优化,这个算是真正实用的dij
关于空间优化是直接使用vect 动态数组,毕竟每次都开静态的二维数组不现实
然后就是使用优先队列,使用优先队列在松弛的时候可以直接找到最短的额那个
边这样就节省了找最短边的过程。
*/ #include<bits/stdc++.h>
using namespace std;
#define maxn 1005
#define INF 0x3f3f3f
struct node
{
int now,len;
bool operator <(const node &a) const
{
return a.len<len;
}
};
int n,m,dis[maxn],use[maxn];
vector<node> mp[maxn];//使用动态数组存放,可以避免很大的空间浪费
void dij(int st)
{
priority_queue<node> qu;
node tmp;
tmp.now=st;
tmp.len=0;
qu.push(tmp); dis[st]=0;
while(qu.size())
{
tmp=qu.top();
qu.pop();
if(use[tmp.now]==1)
continue;
use[tmp.now]=1; int l=mp[tmp.now].size();
for(int i=0;i<l;i++)
{
if(dis[mp[tmp.now][i].now]>dis[tmp.now]+mp[tmp.now][i].len)
{
dis[mp[tmp.now][i].now]=dis[tmp.now]+mp[tmp.now][i].len;
node next;
next.now=mp[tmp.now][i].now;
next.len=dis[next.now];
qu.push(next);
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m),n|m)
{
for(int i=1;i<=n;i++)
{
dis[i]=INF;
mp[i].clear();
}
memset(use,0,sizeof(use)); node tmp;
for(int i=1;i<=m;i++)
{
int x,y,len;
scanf("%d%d%d",&x,&y,&len);
tmp.now=y,tmp.len=len;
mp[x].push_back(tmp);
tmp.now=x;
mp[y].push_back(tmp);
}
dij(1);
printf("%d\n",dis[n]);
}
return 0;
}



Floyd算法   N^3的复杂度
/*
floyd算法可以算出任意两个点之间的最小距离,但是要注意的是floyd
的时间复杂度是n^3数据量比较大的时候很容易超时,一般不会直接使用
floydfloyd只是用来处理一些数据
*/ #include<bits/stdc++.h>
using namespace std;
int n,m;
const int maxn = 110;
const int INF = 0x3f3f3f3f;
int maps[maxn][maxn]; void pre_maps()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
maps[i][j] = INF;
while(m--)
{
int a,b,len;
scanf("%d%d%d",&a,&b,&len);
if(len < maps[a][b])
maps[a][b] = maps[b][a] = len;
}
} void Floyd()
{
for(int k=1;k<=n;k++)//这里要注意一下,作为中间的那个点必须要放在最外层
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(maps[i][j] > maps[i][k] + maps[k][j])
maps[i][j] = maps[i][k] + maps[k][j];
} int main()
{
while(scanf("%d%d",&n,&m) && (n+m))
{
pre_maps();
Floyd();
printf("%d\n",maps[1][n]);
}
return 0;
}



SPFA算法算法复杂度和优化过的dij差不多。
#include<bits/stdc++.h>
using namespace std;
#define maxn 1005
#define INF 0x3f3f3f
struct node
{
int to,len;
};
int n,m,use[maxn],dis[maxn];//这里use是记录这个点是否在队列中,如果在队列中就不再压入了除了队列要将标记消除
vector<node> mp[maxn];
void spfa(int st)
{
dis[st]=0;
use[st]=1;
queue<int> qu;
qu.push(st);
int now,next;
while(qu.size())
{
now=qu.front();
qu.pop();
use[now]=0;
int l=mp[now].size();
for(int i=0;i<l;i++)
{
next=mp[now][i].to;
int len=dis[now]+mp[now][i].len; if(dis[next]>len)
{
dis[next]=len;
if(use[next]==0)
{
qu.push(next);
use[next]=1;
}
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m),n|m)
{
for(int i=1;i<=n;i++)
{
dis[i]=INF;
mp[i].clear();
}
memset(use,0,sizeof(use));
node now;
for(int i=1;i<=m;i++)
{
int x,y,len;
scanf("%d%d%d",&x,&y,&len); now.to=y,now.len=len;
mp[x].push_back(now);
now.to=x;
mp[y].push_back(now);
}
spfa(1);
printf("%d\n",dis[n]);
}
return 0;
}