图论算法之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()
{
scanf("%d%d%d%d",&n,&m,&u,&t);//n是节点数,m是边数,u是起点,t是终点
for (i=1;i<=m;++i)
{
scanf("%d%d%d",&x,&y,&z);
a[x][y]=z;a[y][x]=z;
}
for (i=1;i<=n;++i) s[i]=0x7777777;
s[u]=0;
for (i=1;i<=n-1;++i)
{
Min=0x7777777;
for (j=1;j<=n;++j)
if (!b[j]&&Min>s[j])
{
Min=s[j];
minj=j;
}
b[minj]=true;
for (j=1;j<=n;++j)
if (!b[j]&&a[minj][j])
if (a[minj][j]+s[minj]<s[j]) s[j]=a[minj][j]+s[minj];
}
if (s[t]!=0x7777777)
{
printf("%d",s[t]);
return 0;
}
else
printf("-1");
return 0;
}
【单源最短路径的输出】
依然是设前驱;
每次更新之后pre[j]=minj;递归输出;
例1 【codevs1557/tyvj1031/USCAO OTC09 9TH】热浪
描述
FJ已经研究过可以把牛奶从威斯康星运送到德克萨斯州的路线。这些路线包括起始点和终点先一共经过T (1 <= T <= 2,500)个城镇,方便地标号為1到T。除了起点和终点外地每个城镇由两条双向道路连向至少两个其它地城镇。每条道路有一个通过费用(包括油费,过路费等等)。考虑这个有7个城镇的地图。城镇5是奶源,城镇4是终点(括号内的数字是道路的通过费用)。
[1]----1---[3]-
/ \
[3]---6---[4]---3--[3]--4
/ / /|
5 --[3]-- --[2]- |
\ / / |
[5]---7---[2]--2---[3]---
| /
[1]------
经过路线5-6-3-4总共需要花费3 (5->6) + 4 (6->3) + 3 (3->4) = 10的费用。
给定一个地图,包含C (1 <= C <= 6,200)条直接连接2个城镇的道路。每条道路由道路的起点Rs,终点Re (1 <= Rs <= T; 1 <= Re <= T),和花费(1 <= Ci <= 1,000)组成。求从起始的城镇Ts (1 <= Ts <= T)到终点的城镇Te(1 <= Te <= T)最小的总费用。
输入格式
* 第2到第C+1行: 第i+1行描述第i条道路。有3个由空格隔开的整数: Rs, Re和Ci
输出格式
——译者注)数据保证至少存在一条道路。
测试样例1
输入
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位。
测试样例1
输入
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这样做过了。。。有兴趣可以自行思考。。。