Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 82480 Accepted Submission(s): 35690
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 11 2 33 31 2 52 3 53 1 20 0
Sample Output
32
跟这道题打了三天架,终于把所有最短路算法总结到一起,话不多说,来一波代码。
Floyd:
/*Floyd是十分暴力的一个算法,复杂度相当高,但是十分简单,并且可以解决负权问题,
其主体思想为让每个点都做一次跳板,去松弛所有的,这样可以求出来任意两个点之间
的最短路,用邻接矩阵实现*/
#include
using namespace std;
#define inf 0x3f3f3f3f //概念上的无穷大
int n,m,map[105][105]; //邻接矩阵存地图
int main()
{
int i,j,k;
int x,y,z;
while(cin>>n>>m && n || m)
{
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(i==j)
map[i][j]=0;
else
map[i][j]=inf; //初始化
}
for(i=1;i<=m;i++)
{
cin>>x>>y>>z;
if(map[x][y]>z) //解决重边的问题
{
map[x][y]=map[y][x]=z; //构造无向图
}
}
for(k=1;k<=n;k++) //每个点都做一次跳板
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(map[i][j]>map[i][k]+map[k][j]) //对图中任意所有的点进行松弛
map[i][j]=map[i][k]+map[k][j];
}
}
cout<
不难看出这个的效率是十分低的,那让我们来看另一种算法
Dijkstra:
/*Dijstra相对于Floyd效率还是高了很多的,它的主题思想为
从源点出发,找距离它最短的一个点,再以这个点为跳板,找离跳板最近的点
当不能继续进行时,再次回到源点,找第二小的点,重复操作,直到所有的点
都被访问过为止,注意,每个点只能访问一次,不然会和Floyd一样复杂*/
#include
using namespace std;
#define inf 0x3f3f3f3f
int map[105][105],dis[105],vis[105]; //map存地图,dis存源点到当前点的距离,vis存访问状态
int n,m;
void dijkstra(int start)
{
int i,j,k;
for(i=1; i<=n; i++)
{
dis[i]=map[start][i]; //对dis进行初始化
}
for(i=1;i<=n;i++)
vis[i]=0; //0表示没有被访问,1表示被访问
vis[start]=1;
for(i=1;i<=n-1;i++) //因为最多访问n-1个点,所以循环n-1次
{
int mix=inf;
int u;
for(j=1;j<=n;j++)
{
if(vis[j]==0 && mix>dis[j])
{
mix=dis[j]; //记录下距离源点最近的点,并且没有被访问
u=j;
}
}
vis[u]=1; //标记为已经被访问
for(k=1;k<=n;k++)
{
if(vis[k]==0 && dis[k]>dis[u]+map[u][k])
dis[k]=dis[u]+map[u][k]; //以该点为跳板,对所有点进行松弛
}
}
}
int main()
{
int i,j,k;
while(cin>>n>>m && n || m)
{
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(i==j)
map[i][j]=0;
else
map[i][j]=inf; //初始化
}
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
if(map[x][y]>z)
{
map[x][y]=map[y][x]=z; //无向图
}
}
dijkstra(1);
cout<
Dijkstra算法虽然好,但是并不能解决负权问题,更准确的说是判断有没有负权的存在,于是就有了另一种算法,Bellman_Fod:
/*Bellman_Ford的主体思想为对每一条边进行松弛操作,并且是重复进行,最多
重复n-1次,如果n-1后还可以进行松弛,说明有负权的存在,但是它并不能解决
负权问题*/
#include
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int u[2*max+10],v[2*max+10],w[2*max+10],dis[105];
int main() { //u存起点,v存终点,w存权值,dis和迪杰一样,由于是无向图,所以要 *2(我在这里犯了一次错)
int n,m,i,j,k;
int x,y,z;
while(cin>>n>>m && n || m) {
for(i=1; i<=2*m; i++) {
cin>>x>>y>>z;
u[i]=x;
v[i]=y;
w[i]=z;
j=i+1; //构造无向图
u[j]=y;
v[j]=x;
w[j]=z;
i++;
}
for(i=1; i<=n; i++)
dis[i]=inf;
dis[1]=0; //将起点设置为零,这样可以保证从起点开始松弛
for(k=1; k<=n-1; k++) { //最多循环n-1次
for(i=1; i<=2*m; i++) {
if(dis[v[i]]>dis[u[i]]+w[i])
dis[v[i]]=dis[u[i]]+w[i]; //对所有的边进行松弛操作
}
}
for(i=1; i<=2*m; i++) {
if(dis[v[i]]>dis[u[i]]+w[i])
return false; //这里检测有没有负权,不过本题用不到
}
cout<
看到这里我想大家一定也发现了一个问题,写法太朴素了,而且多了好多不必要的计算,那么,我们就有对它们的优化,首先,先来看一下邻接矩阵版本的Bellman_Ford的队列优化,即SPFA 1.0:
/*SPFA相比较于之前的写法减少了许多不必要的计算,其主体思路为,首先先将起点
放入队列,然后将它拿出,对其他点进行松弛,如果松弛成功,则将其放进队列(前提是它不在队列中)
每一次都把队首的点拿出来进行松弛,直到队列为空,注意,点可以重复入队,如果入队超过n次,说明有
负权*/
#include
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
int m,n;
int map[105][105],dis[105],vis[105],num[105]; //vis表示是否在队列中,num表示入队的次数
queue q;
int SPFA(int start)
{
int i,j,k,temp;
for(i=1;i<=n;i++)
dis[i]=inf;
memset(num,0,sizeof(num));
memset(vis,0,sizeof(vis));
dis[start]=0; //将起点初始化为零,保证从这一点开始松弛
q.push(start);
while(!q.empty())
{
temp=q.front(); //取队首
q.pop(); //拿出
for(i=1;i<=n;i++)
{
if(dis[i]>map[temp][i]+dis[temp])
{
dis[i]=dis[temp]+map[temp][i]; //松弛
if(!vis[i]) //入队
{
q.push(i);
vis[i]=1;
num[i]++; //判断是否存在负环
if(num[i]>n)
return false;
}
}
vis[temp]=0; //标记出队
}
}
}
int main()
{
int i,j,k;
int a,b,c;
while(cin>>n>>m && n || m)
{
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(i==j)
map[i][j]=0;
else
map[i][j]=inf; //初始化
}
for(i=1;i<=m;i++)
{
cin>>a>>b>>c;
if(map[a][b]>c)
map[a][b]=map[b][a]=c; //无向图
}
SPFA(1);
cout<
虽然说这解决了许多不必要的计算,但是用邻接矩阵是十分浪费空间的,于是就有了SPFA2.0(链式前向星):
/*这种写法的优势在于可以节省大量的空间,用到了结构体,会用到一个head
数组,初始化都为-1,代表没有点与该点相连,如果有点与它相连,那就让head
指向它,然后让这个点的next(结构体变量)指向起点即-1,代表该点是最后一个
和头点相接的点,如果有新的点进来,就把它插入到两者之间,让后进来的点指向
先进来的点,头点(起点)指向新进来的点,这样,最后一个点的next值一定为-1
这样可以对每个边进行松弛*/
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int dis[105],head[105],vis[105];
int n,m,ans;
struct node {
int v,w,next; //v为要到达的点,w为权值,next为指向的下一个点
} edge[2*max+5]; //无向图 *2
void add(int from,int to,int cost) {
edge[ans].v=to;
edge[ans].w=cost; //添加新的点
edge[ans].next=head[from]; //ans代表第几条边
head[from]=ans++; //head指向最新的点(第几条边)
}
bool spfa(int start) {
for(int i=1; i<=n; i++)
dis[i]=inf;
dis[start]=0; //除源点外,其他均为inf
for(int i=1; i<=n; i++)
vis[i]=0;
queue q;
q.push(start);
vis[start]=1; //源点入队
while(!q.empty()) {
int temp=q.front(); //出队
q.pop();
vis[temp]=0;
for(int i=head[temp]; i!=-1; i=edge[i].next) { //对每个点所指向的点一一进行处理,i=-1时说明已经没有点与之相连
int a=edge[i].v;
int b=edge[i].w;
if(dis[a]>dis[temp]+b) {
dis[a]=dis[temp]+b;
if(vis[a]==0) {
q.push(a);
vis[a]=1;
}
}
}
}
}
int main() {
int x,y,z;
int i,j,k;
while(scanf("%d%d",&n,&m) !=EOF && n || m) {
ans=1;
for(i=1; i<=n; i++)
head[i]=-1;
for(i=1; i<=m; i++) {
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z); //无向图
}
spfa(1);
cout<
这个一样可以判断是否存在负环,我忘记谢了,操作和1.0一样。
既然Bellman_Ford算法可以进行优化,那么Dijkstra一样进行优化,用到了优先队列:
/*Dijkstra优先队列的优化,运用了重载函数可以优先使最大值或者最小值
出队列的原理进行优化,节省了大量的空间和时间,主体思路为用vector分别
存储边和权,用结构体中的元素做跳板,操作过程为,先将起点放入优先队列(先放到结构体中),
然从该点开始找它可以松弛的点,然后全部暂时存到结构体中,然后放入优先队列中,
然后将起点拿出,优先队列自动接着会吧最小的拿出来进行松弛,直到队列为空,
注意,一个点只能入队一次*/
#include
#include
#include
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
#define max 10000
int n,m,dis[105],vis[105];
vector e[2*max+10];//边
vector w[2*max+10];//权 //无向图 *2
struct P {
int u,d;
bool operator <(const P &a) const {
return d>a.d; //函数重载实现每次优先拿最小值
}
};
void dijkstra(int s) {
int i,k,j;
for(i=1; i<=n; i++)
dis[i]=inf; //初始化
dis[s]=0;
memset(vis,0,sizeof(vis));
priority_queue q;
P tn;
tn.u=s;
tn.d=0; //暂时存到结构体里面,以便优先队列发挥作用
q.push(tn);
while(!q.empty()) {
P t;
t=q.top();
q.pop();
if(vis[t.u]) continue; //如果已经访问过,直接取下一个点
vis[t.u]=1;
int u=t.u;
for(i=0; idis[u]+w[u][i]) {
dis[v]=dis[u]+w[u][i];
tn.d=dis[v]; //暂时存到结构体中,以便放入队列
tn.u=v;
q.push(tn);
}
}
}
}
int main() {
int i,j,k;
int a,b,c;
while(cin>>n>>m && n || m) {
for(i=0; i<=2*m; i++) {
e[i].clear();
w[i].clear(); //一定要初始化,第一次我就因为这个WA了
}
for(i=1; i<=m; i++) {
cin>>a>>b>>c;
e[a].push_back(b);
w[a].push_back(c);
e[b].push_back(a); //无向图
w[b].push_back(c);
}
dijkstra(1);
cout<
同样, 它也有链式前向星的写法:
#include
#include
#include
#include
using namespace std;
#define inf 0x3f3f3f
long long dis[10005];
int head[10005];
int cnt;
struct node
{
int v;
int w;
int next;
}edge[20005];
struct tnode
{
int v;//当前点
int w;//当前点到起点的距离
friend bool operator < (tnode a,tnode b)
{
return a.w>b.w;
}
}temp,now;//用于存储暂时用到的点
void add(int from,int to,int val)//链式前向星存储
{
edge[cnt].v=to;
edge[cnt].w=val;
edge[cnt].next=head[from];
head[from]=cnt++;
}
void dji(int s,int e)
{
priority_queue q;
temp.v=s;//存起点
temp.w=0;
q.push(temp);
while(!q.empty())
{
temp=q.top();//每次取距离起点最近的点
q.pop();
int t=temp.v;
if(dis[t]dis[temp.v]+now.w)
{
dis[now.v]=dis[temp.v]+now.w;
q.push(now);
}
}
}
}
int main()
{
int t,n,i,j,k;
int a,b,c;
while(cin>>n>>t)
{
cnt=1;
memset(head,-1,sizeof(head));
memset(dis,0x3f,sizeof(dis));
dis[n]=0;
while(t--)
{
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dji(1,n);
cout<