继续复习数据结构和算法,总结一下求解最短路径的一些算法。

弗洛伊德(floyd)算法

弗洛伊德算法是最容易理解的最短路径算法,可以求图中任意两点间的最短距离,但时间复杂度高达\(O(n^3)\),主要思想就是如果想缩短从一个点到另一个点的距离,就必须借助一个中间点进行中转,比如A点到B点借助C点中转的话AB的距离就可以更新为\(D(a,b)=Min(D(a,b),D(a,c)+D(c,b))\),这样我们用每一个结点作为中转结点,尝试对另每两个结点进行距离更新,总共需要三层循环进行遍历。

核心代码如下,图存储在邻接矩阵G中。

	for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
for (int k = 0; k < n; k++)
{
G[j][k] = min(G[j][k], G[j][i] + G[i][k]);
}
}
}

迪杰斯特拉(Dijkstra)算法

迪杰斯特拉算法是一种求解单源最短路径的算法,给定一个结点,可以求出图上各个结点到该结点最短距离。

没学过的话推荐看看这个视频:https://www.bilibili.com/video/av21376839?p=13,从8分钟开始。看完之后基本上就明白了Dijkstra算法的运行过程。总结一下就是不断寻找离源点最近的点并将其作为新的源点去更新其他点到目标点的距离。

代码如下,nowIndex代表当前源点编号,minDis是当前源点到其他点的最短距离,用于选择下一个源点,dis数组存储每个点到最终目标点的距离,也就是结果,mark数组用于标记结点是否被当作源点过。

#include<iostream>
#include<algorithm>
using namespace std; #define inf 100000000 int G[10][10];
int dis[10];
bool mark[10];
int n, m; void dijkstra(int nowIndex)
{
mark[nowIndex] = true;
for (int i = 1; i <= n; i++)//先将跟源点直接相连的结点更新一遍
dis[i] = min(dis[i], G[nowIndex][i]);
for (int i = 1; i < n; i++)//循环n-1次,因为源点已经更新过了
{
int minDis = inf;
for (int j = 1; j <= n; j++)//找离当前源点最近的点
{
if (!mark[j] && dis[j] < minDis)
{
minDis = dis[j];
nowIndex = j;
}
}
mark[nowIndex] = true;
for (int j = 1; j <= n; j++)//用当前源点去更新
dis[j] = min(dis[j], dis[nowIndex] + G[nowIndex][j]);
}
} int main()
{
cin >> n >> m;//输入顶点数和边数
int u, v, w;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (i != j)
G[i][j] = inf;
else
G[i][j] = 0;
for (int i = 1; i <= n; i++)
dis[i] = inf;
for (int i = 0; i < m; i++)
{
cin >> u >> v >> w;//输入无向边
G[u][v] = w;
G[v][u] = w;
}
dijkstra(1);//以1号结点为源点
for (int i = 1; i <= n; i++)
{
cout << dis[i] << ' ';
} return 0;
}

邻接表实现

使用邻接表存储图能大大降低空间复杂度,代码如下:

#include<iostream>
#include<algorithm>
using namespace std; #define inf 100000000
#define maxN 10000 int value[maxN], to[maxN], nextL[maxN];
int head[maxN], total;
int dis[maxN];
bool mark[maxN];
int n, m; void dijkstra(int nowIndex)
{
for (int i = 0; i <= n; i++)dis[i] = inf;
dis[nowIndex] = 0;
mark[nowIndex] = true;
for (int i = head[nowIndex]; i; i = nextL[i])
{
dis[to[i]] = min(dis[to[i]], dis[nowIndex] + value[i]);
}
for (int i = 1; i < n; i++)//循环n-1次,因为源点已经更新过了
{
int minDis = inf;
for (int j = 1; j <= n; j++)//找离当前源点最近的点
{
if (!mark[j] && dis[j] < minDis)
{
minDis = dis[j];
nowIndex = j;
}
}
mark[nowIndex] = true;
for (int j = head[nowIndex]; j; j = nextL[j])
{
dis[to[j]] = min(dis[to[j]], dis[nowIndex] + value[j]);
}
}
} void AddLine(int a, int b, int c)
{
total++;
to[total] = b;
value[total] = c;
nextL[total] = head[a];
head[a] = total;
} int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int a, b, c;
cin >> a >> b >> c;
AddLine(a, b, c);
}
dijkstra(1);
for (int i = 1; i <= n; i++)
cout << dis[i] << " "; return 0;
}

堆优化

普通的Dijkstra时间复杂度为\(O(n^2)\),但可以通过优化达到\(O(nlogn)\),注意在上面的循环中我们每次都要取出离当前源点最近的点,所以可以用优先级队列来优化。每次搜索将修改过dis的点进队,然后每次取队首就是最近的点。

代码如下:

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std; #define inf 100000000
#define maxN 10010 int s;
int value[500001], to[500001], nextL[500001];
int head[maxN], total;
int dis[maxN];
bool mark[maxN];
int n, m; typedef pair<int, int> disID;
priority_queue<disID,vector<disID>,greater<disID>> q; void dijkstra(int nowIndex)
{
for (int i = 0; i <= n; i++)dis[i] = inf;
dis[nowIndex] = 0;
q.push(disID(0, nowIndex));
while (!q.empty())
{
int t = q.top().second;
q.pop();
if (mark[t])continue;
mark[t] = true;
for (int i = head[t]; i ; i=nextL[i])
{
if (dis[to[i]] > dis[t] + value[i])
{
dis[to[i]] = dis[t] + value[i];
q.push(disID(dis[to[i]], to[i]));
}
}
}
} void AddLine(int a, int b, int c)
{
total++;
to[total] = b;
value[total] = c;
nextL[total] = head[a];
head[a] = total;
} int main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
int a, b, c;
cin >> a >> b >> c;
AddLine(a, b, c);
}
dijkstra(s);
for (int i = 1; i <= n; i++)
{
cout << dis[i] << ' ';
} return 0;
}

Bellman-ford算法

上面的Dijkstra算法存在一个问题就是不能处理存在负权边的情况,只要有边的权值是负数就不能用,这时可以用Bellman-ford算法解决。

Bellman-ford算法的思想是这样的,我们将每条边的起点、权值、终点存储为三个数组from[i],val[i],to[i],然后扫描每一条边,看能不能通过走这条边来使dis[to[i]]减少。

代码很简单如下:

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std; #define inf 100000000
int from[10000], val[10000], to[10000];
int dis[10000];
int n, m; void Bellman_ford(int u)
{
for (int i = 0; i <= n; i++)dis[i] = inf;
dis[u] = 0;
while (true)
{
bool update = false;
for (int i = 1; i <= m; i++)
{
if (dis[from[i]] != inf && dis[to[i]] > dis[from[i]] + val[i])
{
dis[to[i]] = dis[from[i]] + val[i];//更新
update = true;
}
}
if (!update)break;//直到每一条边都不能使dis减少
}
} int main()
{
cin >> n >> m ;
for (int i = 1; i <= m; i++)
{
cin >> from[i] >> to[i] >> val[i];
}
Bellman_ford(1);
for (int i = 1; i <= n; i++)
cout << dis[i] << ' ';
return 0;
}

算法中的while循环最多循环n-1次,所以Bellman-ford的时间复杂度是\(O(mn)\),不仅能处理负权边,而且在稀疏图(顶点数远多于边数)当中比Dijkstra快。

最短路径算法总结(floyd,dijkstra,bellman-ford)的更多相关文章

  1. 最小生成树(prime算法 & kruskal算法)和 最短路径算法(floyd算法 & dijkstra算法)

    一.主要内容: 介绍图论中两大经典问题:最小生成树问题以及最短路径问题,以及给出解决每个问题的两种不同算法. 其中最小生成树问题可参考以下题目: 题目1012:畅通工程 http://ac.jobdu ...

  2. 最短路径算法之二——Dijkstra算法

    Dijkstra算法 Dijkstra算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止. 注意该算法要求图中不存在负权边. 首先我们来定义一个二维数组Edge[MAXN][MAXN]来存储 ...

  3. 单元最短路径算法模板汇总(Dijkstra, BF,SPFA),附链式前向星模板

    一:dijkstra算法时间复杂度,用优先级队列优化的话,O((M+N)logN)求单源最短路径,要求所有边的权值非负.若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的 ...

  4. 最短路径算法之一——Floyd算法

    Floyd算法 Floyd算法可以用来解决任意两个顶点之间的最短路径问题. 核心公式为: Edge[i][j]=Min{Edge[i][j],Edge[i][k]+Edge[k][j]}. 即通过对i ...

  5. 多源最短路径算法:Floyd算法

    前言 由于本人太菜,这里不讨论Floyd的正确性. 简介 多源最短路径,解决的是求从图中任意两点之间的最短路径的问题. 分析 代码短小精悍,主要代码只有四行,直接放上: for(int k=1;k&l ...

  6. 最短路径算法——Dijkstra,Bellman-Ford,Floyd-Warshall,Johnson

    根据DSqiu的blog整理出来 :http://dsqiu.iteye.com/blog/1689163 PS:模板是自己写的,如有错误欢迎指出~ 本文内容框架: §1 Dijkstra算法 §2 ...

  7. poj1860 bellman—ford队列优化 Currency Exchange

    Currency Exchange Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 22123   Accepted: 799 ...

  8. 图论——最短路径 Dijkstra算法、Floyd算法

    1.弗洛伊德算法(Floyd) 弗洛伊算法核心就是三重循环,M [ j ] [ k ] 表示从 j 到 k 的路径,而 i 表示当前 j 到 k 可以借助的点:红色部分表示,如果 j 到 i ,i 到 ...

  9. Bellman - Ford 算法解决最短路径问题

    Bellman - Ford 算法: 一:基本算法 对于单源最短路径问题,上一篇文章中介绍了 Dijkstra 算法,但是由于 Dijkstra 算法局限于解决非负权的最短路径问题,对于带负权的图就力 ...

随机推荐

  1. BZOJ1257 [CQOI2007]余数之和 (数论分块)

    题意: 给定n, k,求$\displaystyle \sum_{i=1}^nk\;mod\;i$ n,k<=1e9 思路: 先转化为$\displaystyle \sum_{i=1}^n(k- ...

  2. Expect & Shell: 网络设备配置备份

    1. 环境介绍及效果展示 A. centos 6.6 x64 B. tftp-server 0.49 C. 脚本目录 D. 备份目录 E. 备份邮件 2. tftp服务配置 A. [root@step ...

  3. 3D点云配准算法简述

    ​蝶恋花·槛菊愁烟兰泣露 槛菊愁烟兰泣露,罗幕轻寒,燕子双飞去. 明月不谙离恨苦,斜光到晓穿朱户. 昨夜西风凋碧树,独上高楼,望尽天涯路. 欲寄彩笺兼尺素.山长水阔知何处? --晏殊 导读: 3D点云 ...

  4. php面试笔记(5)-php基础知识-自定义函数及内部函数考点

    本文是根据慕课网Jason老师的课程进行的PHP面试知识点总结和升华,如有侵权请联系我进行删除,email:guoyugygy@163.com 在面试中,考官往往喜欢基础扎实的面试者,而函数相关的考点 ...

  5. linux系统初装

    一.linux系统安装 VMware workstation是一个虚拟机软件,它的主要作用是在原有操作系统(windows或linux)下,虚拟出一台电脑,你可以在这台虚拟电脑上安装不同的操作系统,进 ...

  6. 前缀和&差分

    一:差分数组概念  一.差分数组的定义及用途 1.定义:对于已知有n个元素的数列d,建立记录它每项与前一项差值的差分数组f:显然,f[1]=d[1]-0=d[1];对于整数i∈[2,n],我们让f[i ...

  7. C语言基础二 练习

    指出正确标识符 命名 l 只能由26个英文字母的大小写.10个阿拉伯数字0~9.下划线_组成 l 严格区分大小写,比如test和Test是2个不同的标识符 l 不能以数字开头 l 不可以使用关键字作为 ...

  8. SpringCloud入门学习

    我相信,如果小伙伴们能来到这里,肯定对微服务有一定的认识. 我们之前创建web项目的时候,常见的有两种方式: 1).创建一个war包,然后放在servlet容器中运行(比如Tomcat等); 2).使 ...

  9. java面对对象入门(4)-程序块初始化

    Java实例初始化程序是在执行构造函数代码之前执行的代码块.每当我们创建一个新对象时,这些初始化程序就会运行. 1.实例初始化语法 用花括号创建实例初始化程序块.对象初始化语句写在括号内. publi ...

  10. P3983 赛斯石(赛后强化版)

    链接:Miku ------------- 题目描述一脸懵逼 ------------ 这道题本质上是两个完全背包而已.首先,对于每个船,他所能装的最大货物价值是一定的, 我们可以跑完全背包求出每艘船 ...