迪杰斯特拉(Dijkstra,单源最短路)
Dijkstra算法图文详解
基本原理:
从起始点出发,重复寻找当前距离起始点最近的且未访问过的结点,然后利用该结点更新距离数组,直到访问过全部结点为止,最终的距离数组即为起始点到其余个点的最短路径距离。
特点:
它不允许边有负权值,由于在第一步时就会将与源点最近的点确认为最短,如果这个是可以被缩短的那这个确认是就是错误的。
void dijkstra(int s)
{
memset(dis,MM,sizeof(dis));
dis[s] = 0;
for (int j = 1; j <= n; j++)
{
minn = MM;
vis[s] = 1;
for (int i = 1; i <= n; i++)
{
if (dis[i] < minn && vis[i] == 0)
{
minn = dis[i];
s = i;
}
}
for (int i = first_edge[s]; i != 0; i = edge[i].next)
{
if (vis[edge[i].to] == 0 && dis[s] + edge[i].value < dis[edge[i].to])
{
dis[edge[i].to] = dis[s] + edge[i].value;
pre[edge[i].to] =s;
}
}
}
}
1、题目详情:洛谷P1576 最小花费
在 n n n 个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问 A A A 最少需要多少钱使得转账后 B B B 收到 100 100 100 元。
输入格式
第一行输入两个正整数
n
,
m
n,m
n,m,分别表示总人数和可以互相转账的人的对数。以下
m
m
m 行每行输入三个正整数
x
,
y
,
z
x,y,z
x,y,z,表示标号为
x
x
x 的人和标号为
y
y
y 的人之间互相转账需要扣除
z
%
z\%
z% 的手续费
(
z
<
100
)
(z<100)
(z<100)。
最后一行输入两个正整数
A
,
B
A,B
A,B。数据保证
A
A
A 与
B
B
B 之间可以直接或间接地转账。
输出格式
输出 A A A 使得 B B B 到账 100 100 100 元最少需要的总费用。精确到小数点后 8 8 8 位。
提示
1 ≤ n ≤ 2000 , m ≤ 100000 1\le n \le 2000,m\le 100000 1≤n≤2000,m≤100000。
#include<bits/stdc++.h>
using namespace std;
double a[2222][2222],dis[2222]={0},minn;
int n,m,i,j,k,x,y;
int f[2222]={0};
void dijkstra(int x){
for(int i=1;i<=n;i++)
dis[i]=a[x][i];
dis[x]=1;
f[x]=1;
for(int i=1;i<=n-1;i++){
minn=0;
for(int j=1;j<=n;j++)
if(f[j]==0 && dis[j]>minn){
k=j;
minn=dis[j];
}
f[k]=1;
if(k==y)
break;
for(int j=1;j<=n;j++)
if(f[j]==0 && dis[k]*a[k][j]>dis[j])
dis[j]=dis[k]*a[k][j];
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
scanf("%d%d",&j,&k);
scanf("%lf",&a[j][k]);
a[j][k]=(100-a[j][k])/100;
a[k][j]=a[j][k];
}
cin>>x>>y;
dijkstra(x);
printf("%0.8lf",100/dis[y]);
return 0;
}
弗洛伊德(Floyd,多源最短路)
弗洛伊德(Floyd)算法详解
基本原理:
选取某个节点k作为i到j需要经过的中间节点,通过比较d(i,k)+d(k,j)和现有d(i,j)的大小,将较小值更新为路径长度,对k节点的选取进行遍历,以得到在经过所有节点时i到j的最短路径长度,通过不断加入中间点的方式更新最短路径。这个算法代码十分简洁,优雅,背后的思想是基于动态规划。
特点:
边的权值正值负值均可,但是不可处理有负权环的情况。
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (a[i][j] > a[i][k] + a[k][j])
{
a[i][j] = a[i][k] + a[k][j];
p[i][j] = k;
}
}
}
}
2、题目详情:洛谷P1346 电车
在一个神奇的小镇上有着一个特别的电车网络,它由一些路口和轨道组成,每个路口都连接着若干个轨道,每个轨道都通向一个路口(不排除有的观光轨道转一圈后返回路口的可能)。在每个路口,都有一个开关决定着出去的轨道,每个开关都有一个默认的状态,每辆电车行驶到路口之后,只能从开关所指向的轨道出去,如果电车司机想走另一个轨道,他就必须下车切换开关的状态。
为了行驶向目标地点,电车司机不得不经常下车来切换开关,于是,他们想请你写一个程序,计算一辆从路口
A
A
A 到路口
B
B
B 最少需要下车切换几次开关。
输入格式
第一行有
3
3
3 个整数
N
,
A
,
B
N,A,B
N,A,B(
2
≤
N
≤
100
,
1
≤
A
,
B
≤
N
2 \leq N \leq 100, 1 \leq A,B \leq N
2≤N≤100,1≤A,B≤N),分别表示路口的数量,和电车的起点,终点。
接下来有
N
N
N 行,每行的开头有一个数字
K
i
K_i
Ki(
0
≤
K
i
≤
N
−
1
0 \leq K_i \leq N-1
0≤Ki≤N−1),表示这个路口与
K
i
K_i
Ki 条轨道相连,接下来有
K
i
K_i
Ki 个数字表示每条轨道所通向的路口,开关默认指向第一个数字表示的轨道。
输出格式
输出文件只有一个数字,表示从 A A A 到 B B B 所需的最少的切换开关次数,若无法从 A A A 前往 B B B,输出 − 1 -1 −1。
#include<bits/stdc++.h>
using namespace std;
int main(){
int i,k,j,num,a[110][110],n,from,end,b[110][110];;
cin>>n>>from>>end;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
a[i][j]=99999;
for(i=1;i<=n;i++){//读入
scanf("%d",&num);
for(j=1;j<=num;j++){
scanf("%d",&k);
if(j==1)
a[i][k]=0;
else
a[i][k]=1;
b[i][k]=1;
}
}
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(b[i][k]==1&&b[k][j]==1)
a[i][j]=min(a[i][k]+a[k][j],a[i][j]),b[i][j]=1;
if(b[from][end]==1)
cout<<a[from][end];
else
cout<<"-1";
return 0;
}