算法与数据结构(九) 图论:最短路径问题
最路径问题 Shortest Path
一个节点到另一个节点最短的路径。路径规划问题。
- 路径规划
- 工作任务规划
对于无权图进行广度优先遍历就是求出了一个最短路径。
从起始点到其他节点路径最短的树
无权图的最短路径。
松弛操作。找到更短路径。
dijkstra 单源最短路径算法
前提:图中不能有负权边
复杂度 O( E log(V) )同最小生成树
最小索引堆
从源点能到达的点中最短的路径。
图中不能有负权边
经过2到达1和经过2到达3比原来记录的值小,所以松弛更新。
经过1到4更短
IndexMinHeap
代码实现:
// Dijkstra算法求最短路径
template<typename Graph, typename Weight>
class Dijkstra{private:Graph &G; // 图的引用int s; // 起始点Weight *distTo; // distTo[i]存储从起始点s到i的最短路径长度bool *marked; // 标记数组, 在算法运行过程中标记节点i是否被访问vector<Edge<Weight>*> from; // from[i]记录最短路径中, 到达i点的边是哪一条// 可以用来恢复整个最短路径public:// 构造函数, 使用Dijkstra算法求最短路径Dijkstra(Graph &graph, int s):G(graph){// 算法初始化assert( s >= 0 && s < G.V() );this->s = s;distTo = new Weight[G.V()];marked = new bool[G.V()];for( int i = 0 ; i < G.V() ; i ++ ){distTo[i] = Weight();marked[i] = false;from.push_back(NULL);}// 使用索引堆记录当前找到的到达每个顶点的最短距离IndexMinHeap<Weight> ipq(G.V());// 对于其实点s进行初始化distTo[s] = Weight();from[s] = new Edge<Weight>(s, s, 0);ipq.insert(s, distTo[s] );marked[s] = true;while( !ipq.isEmpty() ){int v = ipq.extractMinIndex();// distTo[v]就是s到v的最短距离marked[v] = true;// 对v的所有相邻节点进行更新typename Graph::adjIterator adj(G, v);for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){int w = e->other(v);// 如果从s点到w点的最短路径还没有找到if( !marked[w] ){// 如果w点以前没有访问过,// 或者访问过, 但是通过当前的v点到w点距离更短, 则进行更新if( from[w] == NULL || distTo[v] + e->wt() < distTo[w] ){distTo[w] = distTo[v] + e->wt();from[w] = e;if( ipq.contain(w) )ipq.change(w, distTo[w] );elseipq.insert(w, distTo[w] );}}}}}// 析构函数~Dijkstra(){delete[] distTo;delete[] marked;delete from[0];}// 返回从s点到w点的最短路径长度Weight shortestPathTo( int w ){assert( w >= 0 && w < G.V() );assert( hasPathTo(w) );return distTo[w];}// 判断从s点到w点是否联通bool hasPathTo( int w ){assert( w >= 0 && w < G.V() );return marked[w];}// 寻找从s到w的最短路径, 将整个路径经过的边存放在vec中void shortestPath( int w, vector<Edge<Weight>> &vec ){assert( w >= 0 && w < G.V() );assert( hasPathTo(w) );// 通过from数组逆向查找到从s到w的路径, 存放到栈中stack<Edge<Weight>*> s;Edge<Weight> *e = from[w];while( e->v() != this->s ){s.push(e);e = from[e->v()];}s.push(e);// 从栈中依次取出元素, 获得顺序的从s到w的路径while( !s.empty() ){e = s.top();vec.push_back( *e );s.pop();}}// 打印出从s点到w点的路径void showPath(int w){assert( w >= 0 && w < G.V() );assert( hasPathTo(w) );vector<Edge<Weight>> vec;shortestPath(w, vec);for( int i = 0 ; i < vec.size() ; i ++ ){cout<<vec[i].v()<<" -> ";if( i == vec.size()-1 )cout<<vec[i].w()<<endl;}}
};
main.cpp:
// 测试我们的Dijkstra最短路径算法
int main() {string filename = "testG1.txt";int V = 5;SparseGraph<int> g = SparseGraph<int>(V, true);// Dijkstra最短路径算法同样适用于有向图//SparseGraph<int> g = SparseGraph<int>(V, false);ReadGraph<SparseGraph<int>, int> readGraph(g, filename);cout<<"Test Dijkstra:"<<endl<<endl;Dijkstra<SparseGraph<int>, int> dij(g,0);for( int i = 0 ; i < V ; i ++ ){if(dij.hasPathTo(i)){cout<<"Shortest Path to "<<i<<" : "<<dij.shortestPathTo(i)<<endl;dij.showPath(i);}elsecout<<"No Path to "<<i<<endl;cout<<"----------"<<endl;}return 0;
}
处理负权边
Bellman-Ford 单源最短路径算法
- 如果一个图没有负权环,
- 从一点到另外一点的最短路径,最多经过所有的V个顶线,有V-1条边
- 否则,存在顶点经过了两次,既存在负权环
松弛操作的核心是我们找到了一条边的路径,我们看一下有没有两条边的路径比他权值小。
对一个点的一次松弛操作,就是找到经过这个点的另外一条路径,多一条边,权值更小。
如果一个图没有负权环,从一点到另外一点的最短路径,最多经过所有的V个顶线,有V-1条边
对所有的点进行V-1次松弛操作
对所有的点进行V-1次松弛操作,理论上就找到了从源点到其他所有点的最短路径。
如果还可以继续松弛,所说原图中有负权环。
// 使用BellmanFord算法求最短路径
template <typename Graph, typename Weight>
class BellmanFord{private:Graph &G; // 图的引用int s; // 起始点Weight* distTo; // distTo[i]存储从起始点s到i的最短路径长度vector<Edge<Weight>*> from; // from[i]记录最短路径中, 到达i点的边是哪一条// 可以用来恢复整个最短路径bool hasNegativeCycle; // 标记图中是否有负权环// 判断图中是否有负权环bool detectNegativeCycle(){for( int i = 0 ; i < G.V() ; i ++ ){typename Graph::adjIterator adj(G,i);for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )if( from[e->v()] && distTo[e->v()] + e->wt() < distTo[e->w()] )return true;}return false;}public:// 构造函数, 使用BellmanFord算法求最短路径BellmanFord(Graph &graph, int s):G(graph){this->s = s;distTo = new Weight[G.V()];// 初始化所有的节点s都不可达, 由from数组来表示for( int i = 0 ; i < G.V() ; i ++ )from.push_back(NULL);// 设置distTo[s] = 0, 并且让from[s]不为NULL, 表示初始s节点可达且距离为0distTo[s] = Weight();from[s] = new Edge<Weight>(s, s, 0); // 这里我们from[s]的内容是new出来的, 注意要在析构函数里delete掉// Bellman-Ford的过程// 进行V-1次循环, 每一次循环求出从起点到其余所有点, 最多使用pass步可到达的最短距离for( int pass = 1 ; pass < G.V() ; pass ++ ){// 每次循环中对所有的边进行一遍松弛操作// 遍历所有边的方式是先遍历所有的顶点, 然后遍历和所有顶点相邻的所有边for( int i = 0 ; i < G.V() ; i ++ ){// 使用我们实现的邻边迭代器遍历和所有顶点相邻的所有边typename Graph::adjIterator adj(G,i);for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() )// 对于每一个边首先判断e->v()可达// 之后看如果e->w()以前没有到达过, 显然我们可以更新distTo[e->w()]// 或者e->w()以前虽然到达过, 但是通过这个e我们可以获得一个更短的距离, 即可以进行一次松弛操作, 我们也可以更新distTo[e->w()]if( from[e->v()] && (!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()]) ){distTo[e->w()] = distTo[e->v()] + e->wt();from[e->w()] = e;}}}hasNegativeCycle = detectNegativeCycle();}// 析构函数~BellmanFord(){delete[] distTo;delete from[s];}// 返回图中是否有负权环bool negativeCycle(){return hasNegativeCycle;}// 返回从s点到w点的最短路径长度Weight shortestPathTo( int w ){assert( w >= 0 && w < G.V() );assert( !hasNegativeCycle );assert( hasPathTo(w) );return distTo[w];}// 判断从s点到w点是否联通bool hasPathTo( int w ){assert( w >= 0 && w < G.V() );return from[w] != NULL;}// 寻找从s到w的最短路径, 将整个路径经过的边存放在vec中void shortestPath( int w, vector<Edge<Weight>> &vec ){assert( w >= 0 && w < G.V() );assert( !hasNegativeCycle );assert( hasPathTo(w) );// 通过from数组逆向查找到从s到w的路径, 存放到栈中stack<Edge<Weight>*> s;Edge<Weight> *e = from[w];while( e->v() != this->s ){s.push(e);e = from[e->v()];}s.push(e);// 从栈中依次取出元素, 获得顺序的从s到w的路径while( !s.empty() ){e = s.top();vec.push_back( *e );s.pop();}}// 打印出从s点到w点的路径void showPath(int w){assert( w >= 0 && w < G.V() );assert( !hasNegativeCycle );assert( hasPathTo(w) );vector<Edge<Weight>> vec;shortestPath(w, vec);for( int i = 0 ; i < vec.size() ; i ++ ){cout<<vec[i].v()<<" -> ";if( i == vec.size()-1 )cout<<vec[i].w()<<endl;}}
};
main.cpp:
// 测试Bellman-Ford算法
int main() {string filename = "testG2.txt";//string filename = "testG_negative_circle.txt";int V = 5;SparseGraph<int> g = SparseGraph<int>(V, true);ReadGraph<SparseGraph<int>, int> readGraph(g, filename);cout<<"Test Bellman-Ford:"<<endl<<endl;BellmanFord<SparseGraph<int>, int> bellmanFord(g,0);if( bellmanFord.negativeCycle() )cout<<"The graph contain negative cycle!"<<endl;elsefor( int i = 1 ; i < V ; i ++ ) {if (bellmanFord.hasPathTo(i)) {cout << "Shortest Path to " << i << " : " << bellmanFord.shortestPathTo(i) << endl;bellmanFord.showPath(i);}elsecout << "No Path to " << i << endl;cout << "----------" << endl;}return 0;
}
运行结果:
用于有向图,因为无向图中一条负权边就等价于两个方向都有,会形成环。
更多和最短路径相关的问题
单源最短路径算法
具体实现,distTo[i] 初始化为“正无穷”
if( from[e->v()] && (!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()]) ){distTo[e->w()] = distTo[e->v()] + e->wt();from[e->w()] = e;}
利用队列数据结构
queue-based bellman-ford算法
所有对最短路径算法
Floyed算法,处理无负权环的图
O( V^3 )
最长路径算法
最长路径问题不能有正权环。
无权图的最长路径问题是指数级难度的。
对于有权图,不能使用Dijkstra求最长路径问题。
可以使用 Bellman-Ford算法。(取负操作)
算法与数据结构(九) 图论:最短路径问题相关推荐
- 单源最短路径算法java_数据结构 - 单源最短路径之迪杰斯特拉(Dijkstra)算法详解(Java)...
给出一个图,求某个端点(goal)到其余端点或者某个端点的最短路径,最容易想到的求法是利用DFS,假设求起点到某个端点走过的平均路径为n条,每个端点的平均邻接端点为m,那求出这个最短路径使用DFS算法 ...
- 沃舍尔算法_[数据结构拾遗]图的最短路径算法
前言 本专题旨在快速了解常见的数据结构和算法. 在需要使用到相应算法时,能够帮助你回忆出常用的实现方案并且知晓其优缺点和适用环境.并不涉及十分具体的实现细节描述. 图的最短路径算法 最短路径问题是图论 ...
- 算法与数据结构(六) 迪杰斯特拉算法的最短路径(Swift版)
上篇博客我们详细的介绍了两种经典的最小生成树的算法,本篇博客我们就来详细的讲一下最短路径的经典算法----迪杰斯特拉算法.首先我们先聊一下什么是最短路径,这个还是比较好理解的.比如我要从北京到济南,而 ...
- 数据结构(九)——最短路径问题
文章目录 1. 单元最短路径问题 1.1 BFS 1.2 Dijkstra 2. 每对顶点间的最短路径 2.1 Floyd 带权路径长度:任意一对顶点间所需要经过的边的权值和. 最短路径:带权路径长度 ...
- python数据结构推荐书-「算法与数据结构」从入门到进阶吐血整理推荐书单
推荐一下「算法与数据结构」从入门到进阶的书单. 一.入门系列 这些书籍通过图片.打比方等通俗易懂的方法来讲述,让你能达到懂一些基础算法,线性表,堆栈,队列,树,图,DP算法,背包问题等,不要求会实现, ...
- 算法与数据结构简单启蒙,我当年学习算法走过的坑
1.碎碎念 我的算法启蒙来自于紫书算法竞赛入门经典,但是不得不说从语言过度到算法,紫书并不是一个很好的开始.当时整本书除了数学和图论其实是看完了的,但真的有印象的大约只有暴力枚举法中枚举排列,子集生成 ...
- 【操作指导 | 代码实现】挑战程序设计竞赛2:算法和数据结构
书籍封面 第一章 前言 1. 本人衷心建议 ~~~~~~ 如果你是一位初学者,我指的是你只会基本的 C/C++ 编程,即使编的很烂,这本书对于你算法和数据结构的提升非常有帮助,所涉及的每一 ...
- 格雷通路 算法 java,Java算法与数据结构教程
北上广容不下肉身, 三四线放不下灵魂, 程序员里没有穷人, 有一种土豪叫 算法工程师. 程序 = 数据结构 + 算法 程序是为了解决实际问题而存在的.然而为了解决问题,必定会使用到某些数据结构以及设计 ...
- 「算法与数据结构」从入门到进阶吐血整理推荐书单
一.入门系列 这些书籍通过图片.打比方等通俗易懂的方法来讲述,让你能达到懂一些基础算法,线性表,堆栈,队列,树,图,DP算法,背包问题等,不要求会实现,但是看过以下这些书对于之后实现算法打下坚实的思维 ...
- 详解校招算法与数据结构
算法与数据结构(java)版 一,数据结构 1,数组和链表 (1)数组 数组是最常见的一种数据结构,它是相同类型的用一个标识符封装到一起的基本类型数据序列或者对象序列.数组使用一个统一的数组名和不同的 ...
最新文章
- groovy 兼容 java,升级Groovy 1.7 - 2.1不兼容
- php-fpm进程数优化方法
- Java 生成有序 UUID
- linux下mqm用户下S开头日志,Linux新建用户,切换后只显示$问题
- 微信扫描二维码和浏览器扫描二维码 ios和Android 分别进入不用的提示页面
- pycharm设置开发模板
- Activity-在ListFragment中为ListView增加空白视图
- 利用线程池单线程下载网页信息
- 有关PHP的可变函数
- stopwords怎么用_【技术】怎么用Python画出好看的词云图?
- 【论文笔记】多智能体强化学习值分解基础论文5篇
- html5人脸登录,基于HTML5 的人脸识别活体认证
- Centos7 镜像资源下载
- ClearCase汇编
- RestTemplate application/octet-stream处理
- 51Nod1740 蜂巢迷宫
- Vue项目中的Emitted value instead of an instance of Error问题
- 中国IT工作者35岁后的发展出路调查报告(3)
- 什么是 CI/CD?(翻译)
- 第三方支付-核心交易之商户结算设计