图论:HDU2544-最短路(最全、最经典的最短路入门及小结)
最短路
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条商店到赛场的路线。
输入保证至少存在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
![](https://pic002.cnblogs.com/images/2012/426620/2012073019540660.gif)
解题心得:
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; }