图论算法之Dijkstra
【算法思路】
s[i]表示起点到i的最短路径的值;
初始时s[起点]赋为0,其余正无穷;
每一次找到一个s[i]最小的点minj,置标记,然后把所有没有标记过的且与点minj相连的点的s值更新一下;重复做,最多做n-1次,就能保证更新了所有的点。但也正是因为这个算法思路的局限性,Dijkstra不适于有负边权的情况;
【代码】
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int n,m,u,t,i,j,x,y,z,Min,minj; int a[105][105],s[105]; bool b[105]; int main() { <span style="white-space:pre"> </span>scanf("%d%d%d%d",&n,&m,&u,&t);//n是节点数,m是边数,u是起点,t是终点 <span style="white-space:pre"> </span>for (i=1;i<=m;++i) <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>scanf("%d%d%d",&x,&y,&z); <span style="white-space:pre"> </span>a[x][y]=z; <span style="white-space:pre"> </span>a[y][x]=z; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>for (i=1;i<=n;++i) s[i]=0x7777777; <span style="white-space:pre"> </span>s[u]=0; <span style="white-space:pre"> </span>for (i=1;i<=n-1;++i) <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>Min=0x7777777; <span style="white-space:pre"> </span>for (j=1;j<=n;++j) <span style="white-space:pre"> </span> if (!b[j]&&Min>s[j]) <span style="white-space:pre"> </span> { <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>Min=s[j]; <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>minj=j; <span style="white-space:pre"> </span> } <span style="white-space:pre"> </span>b[minj]=true; <span style="white-space:pre"> </span>for (j=1;j<=n;++j) <span style="white-space:pre"> </span> if (!b[j]&&a[minj][j]) <span style="white-space:pre"> </span> if (a[minj][j]+s[minj]<s[j]) s[j]=a[minj][j]+s[minj]; <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (s[t]!=0x7777777) <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>printf("%d",s[t]); <span style="white-space:pre"> </span>return 0; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>else <span style="white-space:pre"> </span> printf("-1"); <span style="white-space:pre"> </span>return 0; }
【单源最短路径的输出】
依然是设前驱;
每次更新之后pre[j]=minj;递归输出;
例1 【codevs1557/tyvj1031/USCAO OTC09 9TH】热浪
7 11 5 4
2 4 2
1 4 3
7 2 2
3 4 3
5 7 5
7 3 3
6 1 1
6 3 4
2 4 3
5 6 3
7 2 1
7
标准的Dijkstra,连路径都不用管。。。
【代码】
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int t,c,ts,te,rs,re,ci,i,j,minj,Min; int s[2505],a[2505][2505]; bool b[2505]; int main() { scanf("%d%d%d%d",&t,&c,&ts,&te); for (i=1;i<=c;++i) { scanf("%d%d%d",&rs,&re,&ci); a[rs][re]=ci; a[re][rs]=ci; } for (i=1;i<=t;++i) s[i]=0x7777777; s[ts]=0; minj=ts; for (i=1;i<=t-1;++i) { Min=0x7777777; for (j=1;j<=t;++j) if (!b[j]&&s[j]<Min) { Min=s[j]; minj=j; } b[minj]=true; for (j=1;j<=t;++j) if (!b[j]&&a[minj][j]) if (s[minj]+a[minj][j]<s[j]) s[j]=s[minj]+a[minj][j]; } if (s[te]!=0x7777777) printf("%d",s[te]); return 0; }
例2 【tyvj3287】 最小花费
最小花费
问题描述:
在n个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问A最少需要多少钱使得转账后B收到100元。
第一行输入两个正整数n,m,分别表示总人数和可以互相转账的人的对数。
以下m行每行输入三个正整数x,y,z,表示标号为x的人和标号为y的人之间互相转账需要扣除z%的手续费 (z
最后一行输入两个正整数A,B。数据保证A与B之间可以直接或间接地转账。
输出A使得B到账100元最少需要的总费用。精确到小数点后8位。
3 3
1 2 1
2 3 2
1 3 3
1 3
103.07153164
时间限制:
各测试点1秒。
内存限制:
你的程序将被分配40MB的运行空间。
数据规模:
1
Dijkstra求最短路;
需要把起点和终点反一下,易知应该是从后往前推回去;
中间的过程不是简单的求最短路(相加),而是需要做一下特殊处理;
【代码】
#include<iostream> #include<cstring> #include<cstdio> using namespace std; double s[2005]; int n,m,i,j,x,y,z,u,t,Min,minj; int a[2005][2005]; bool b[2005]; int main() { scanf("%d%d",&n,&m); for (i=1;i<=n;++i) for (j=1;j<=n;++j) a[i][j]=0x7777777; for (i=1;i<=m;++i) { scanf("%d%d%d",&x,&y,&z); a[x][y]=z; a[y][x]=z; } scanf("%d%d",&t,&u); for (i=1;i<=n;++i) s[i]=0x7777777; s[u]=100; minj=u; for (i=1;i<=n-1;++i) { Min=0x7777777; for (j=1;j<=n;++j) if (!b[j]&&s[j]<Min) { Min=s[j]; minj=j; } b[minj]=true; for (j=1;j<=n;++j) if(a[minj][j]!=0x7777777&&!b[j]&&(s[minj]/(100-a[minj][j])*100+0.00000000<s[j])) s[j]=s[minj]/(100-a[minj][j])*100+0.00000000; } printf("%0.8lf",s[t]); return 0; }不过有人说可以初值赋为100-z,然后跑一边最长路,但是正确性并没有严格的证明,但是据说hxy这样做过了。。。有兴趣可以自行思考。。。