一、问题定义
求解单元点的最短路径问题:给定带权有向图G和源点v,求v到G中其他顶点的最短路径
限制条件:图G中不存在负权值的边
二、思想
划重点,迪杰斯特拉最最朴素的思想就是按长度递增的次序产生最短路径。即每次对所有可见点的路径长度进行排序后,选择一条最短的路径,这条路径就是对应顶点到源点的最短路径。
Tips:可见点就是从源点开始按广度优先算法遍历顶点的过程中,搜索到的点。
下面来解释一下为什么源点到所有可见点的路径中长度最短的一条就一定是源点到该点的最短路径,会不会存在通过一些当前不可见的点间接到达该点的路径比它短呢?
我们设图G的顶点集合为V,再设一个集合S表示已求得最短路径的终点的集合(S怎么来的下面再说)。
设下一条最短路径(终点为x),那么它只能是弧(v,x)或者通过S中的顶点到达x即(v,vi,....,x)。我们来证明一下:
假设(v,...,x)路径上有一个顶点不在S中,则说明存在一条终点不在S中而长度比此路径还短的路径。但这是不可能的。因为我们按长度递增的顺序来产生各最短路径,所以长度比此路径还短的所有路径均已产生,他们的终点一定在S中。
三、算法
1.初始化。V为G中所有顶点集合,S={v}。D[x]表示从源点到x的已知路径,初始D[v]为0,其余为无穷大。
2.从源点v开始运行一步广度优先算法即找其相邻点。如下图中从源点0开始,找到的可见点为1,2,3.
3.计算可见点到源点v的路径长度,更新D[x]。然后对路径进行排序,选择最短的一条作为确定找到的最短路径,将其终点加入到S中,如此处找到的点为2,故将2加入S。S={v,2}.
4.从S中选择新加入的点运行广度优先算法找其相邻点,重复step3。直至所有点已加入S或者再搜索不到新的可见点(图中存在不联通的点,此时S<V)终止算法。
总结一下:
迪杰斯特拉算法总共就干了两件事:
【1】不断运行广度优先算法找可见点,计算可见点到源点的距离长度
【2】从当前已知的路径中选择长度最短的将其顶点加入S作为确定找到的最短路径的顶点。
例题:
问题 C: 【一本通图 最短路径算法】最小花费
[题目描述]
在n个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问A最少需要多少钱使得转账后B收到100元。
输入
第一行输入两个正整数n,m,分别表示总人数和可以互相转账的人的对数。以下m行每行输入三个正整数x,y,z,表示标号为x的人和标号为y的人之间互相转账需要扣除z%的手续费 (z<100)。最后一行输入两个正整数A,B。数据保证A与B之间可以直接或间接地转账。
1<=n<=2000
输出
输出A使得B到账100元最少需要的总费用。精确到小数点后8位。
样例输入
3 3 1 2 1 2 3 2 1 3 3 1 3
样例输出
103.07153164
就是从一个点到边,不断更新。
代码如下:
#include <bits/stdc++.h>
#include <algorithm>
using namespace std;
int n, m, x, y, z, a, b;
double ljjz[2001][2001];
double c[2001];
bool vis[2001];
void dijkstra()
{
c[a] = 1;
for(int i = 1; i <= n; i++)
{
int k = -1;
for (int j = 1; j <= n; j++)
{
if (!vis[j] && c[k] < c[j])
k = j;
}
vis[k] = true;
for(int j = 1; j <= n; j++)
c[j] = max(c[j], c[k] * ljjz[k][j]);
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; i++)
{
cin >> x >> y >> z;
double t = (100.0 - (int)z) / 100;
ljjz[x][y] = ljjz[y][x] = max(ljjz[x][y], t);
}
cin >> a >> b;
dijkstra();
printf("%.8lf\n", 100.0 / c[b]);
//cout << c[b] << endl;
return 0;
}