迪杰斯特拉(Dijkstra,单源最短路)
算法使用了广度优先搜索解决非负权有向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。
该算法的输入包含了一个有权重的有向图 G,以及G中的一个来源顶点 S。我们以 V 表示 G 中所有顶点的集合。每一个图中的边,都是两个顶点所形成的有序元素对。(u, v) 表示从顶点 u 到 v 有路径相连。我们以 E 表示G中所有边的集合,而边的权重则由权重函数 w: E → [0, ∞] 定义。因此,w(u, v) 就是从顶点 u 到顶点 v 的非负权重(weight)。边的权重可以想像成两个顶点之间的距离。任两点间路径的权重,就是该路径上所有边的权重总和。已知有 V 中有顶点 s 及 t,Dijkstra 算法可以找到 s 到 t的最低权重路径(例如,最短路径)。这个算法也可以在一个图中,找到从一个顶点 s 到任何其他顶点的最短路径。对于不含负权的有向图,Dijkstra算法是目前已知的最快的单源最短路径算法。
算法步骤
1. 初始时令 S={V0},T={其余顶点},T中顶点对应的距离值
若存在<v0,vi>,d(V0,Vi)为<v0,vi>弧上的权值
若不存在<v0,vi>,d(V0,Vi)为∞
2. 从T中选取一个其距离值为最小的顶点W且不在S中,加入S
3. 对其余T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值
重复上述步骤2、3,直到S中包含所有顶点,即W=Vi为止
基本原理
从起始点出发,重复寻找当前距离起始点最近的且未访问过的结点,然后利用该结点更新距离数组,直到访问过全部结点为止,最终的距离数组即为起始点到其余个点的最短路径距离。
特点
它不允许边有负权值,由于在第一步时就会将与源点最近的点确认为最短,如果这个是可以被缩短的那这个确认是就是错误的。
代码
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;
}
}
}
}
例题
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,m;
bool vis[2005];
double f[2005][2005],dis[2005];
void dijkstra(int s){
dis[s]=1;
int v;
for(int i=1;i<=n;i++){
double maxx=0;
for(int j=1;j<=n;j++){
if(!vis[j] && maxx<dis[j]){
maxx=dis[j];
v=j;
}
}
vis[v]=1;
for(int j=1;j<=n;j++)
if(!vis[j] && f[v][j]>0 && dis[j]<dis[v]*f[v][j])
dis[j]=dis[v]*f[v][j];
}
}
int main(){
int x,y,z;
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>x>>y>>z;
f[x][y]=f[y][x]=1-z*1.0/100;
}
int s,e;
cin>>s>>e;
dijkstra(s);
printf("%.8f\n",100/dis[e]);
return 0;
}
弗洛伊德(Floyd,多源最短路)
Floyd算法是一个经典的动态规划算法,它又被称为插点法。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。Floyd算法是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,算法目标是寻找从点i到点j的最短路径。
从任意节点i到任意节点j的最短路径不外乎2种可能,1是直接从i到j,2是从i经过若干个节点k到j。所以,算法假设Dis(i,j)为节点u到节点v的最短路径的距离,对于每一个节点k,算法检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
算法步骤
1,从任意一条单边路径开始。所有两点之间的距离是边的权,如果两点之间没有边相连,则权为无穷大。
2,对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比已知的路径更短。如果是更新它。
把图用邻接矩阵G表示出来,如果从Vi到Vj有路可达,则G[i][j]=d,d表示该路的长度;否则G[i][j]=无穷大。定义一个矩阵D用来记录所插入点的信息,D[i][j]表示从Vi到Vj需要经过的点,初始化D[i][j]=j。把各个顶点插入图中,比较插点后的距离与原来的距离,G[i][j] = min( G[i][j], G[i][k]+G[k][j] ),如果G[i][j]的值变小,则D[i][j]=k。在G中包含有两点之间最短道路的信息,而在D中则包含了最短通路径的信息。
基本原理
选取某个节点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;
}
}
}
}
例题
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n, a, b;
int m[105][105];
int main(){
cin >> n >> a >> b;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
m[i][j] = 0x7ffffff;
}
}
for (int i = 1; i <= n; i++)
{
int k;
cin >> k;
for (int j = 1; j <= k; j++)
{
int next;
cin >> next;
if (j == 1)
m[i][next] = 0;
else
m[i][next] = 1;
}
}
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
if (i == k)
continue;
for (int j = 1; j <= n; j++)
{
if (i == j || k == j)
continue;
if (m[i][j] > m[i][k] + m[k][j])
{
m[i][j] = m[i][k] + m[k][j];
}
}
}
}
if (m[a][b] == 0x7ffffff)
{
cout << "-1";
return 0;
}
cout << m[a][b];
return 0;
}