单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。在弄清楚如何求算单源最短路径问题之前,必须弄清楚最短路径的最优子结构性质。 一.最短路径的最优子结构性质 该性质描述为:如果P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,k和s是这条路径上的一个中间顶点,那么P(k,s)必定是从k到s的最短路径 那么(Vi...Vk)也必定是从i到k的最短路径。为了求出最短路径,Dijkstra就提出了以最短路径长度递增,逐次生成最短路径的算法。 譬如对于源顶点V0,首先选择其直接相邻的顶点中长度最短的顶点Vi,那么当前已知可得由V0经过Vi到达与Vi直接相邻的顶点的最短距离dist[j]=min{matrix[V0][j],dist[i]+matrix 根据这种思路, 假设存在G=<V,E>,源顶点为V0,U={V0},dist[i]记录V0到i的最短距离,path[i]记录从V0到i路径上的i前面的一个顶点。
以下为找到一条单源最短路径的思想与思路描述 自己最近看了一下关于单源最短路径的算法,其基础是DijKstra算法:从某个起点开始,选择直接连接的最短路径点,更新最短路径长并逐渐扩到终点。 如图所示的路径:(手工画图,若丑勿怪) 起点为1,寻找到终点3,则操作如下: 一、1找到直接相连的点及其路径长:2(9)、4(6)、5(11),更新点的最短路径数据,此时4(6)路径最短,则以4(6) 为起点寻找; 二、4找到5(6+6=12),此时12 > 11不更新,则4(6)点寻找完毕,未结束; 三、此时已找的最短路径点为2(9),则点2可找到点3(9+12=21)并更新,此时2(9)点寻找完毕 为最短路径且3为终点(此处只有点3),寻找完毕; 总结: 对每个点存储到该点对应的最短路径,如果有最短的路径则更新(初始每个路径长为无穷大); 从起点开始寻找可直接连接的点并更新路径; 如果无直接连接点或无更新点 ,则改点寻找结束,并找下一个有最短路径点开始; 如果有最短路径的点则再次寻找并更新路径; 如果找到终点且该终点路径为可继续寻找路径的点里的最短路径,则该路径为单源最短路径;
现在是0到各节点的路径是: 0-1=10 0-2=60 0-3=30 0-4=100 这里0-3是最短的,因此S集合添加节点3,同样移除T中,3。 那么0到各点的最短路径为 0-1=10 0-2=50 0-3=30 0-4=60 ? { //接受一个有向图的权重矩阵,和一个起点编号start(从0编号,顶点存在数组中) //返回一个int[] 数组,表示从start到它的最短路径长度 int[] visited = new int[n]; //标记当前该顶点的最短路径是否已经求出,1表示已求出 //初始化,第一个顶点求出 k = i; } } //将新选出的顶点标记为已求出最短路径
当然这只是最基础的应用,关于单源最短路径还有很多变体: 1.单源最短路径 2.单目的地最短路径 3.单节点对最短路径 4.所有节点对最短路径 最短路径定义: 路径p=<v0, v1, … vk>的权是指组成 p的所有边的权值之和 从u到v的最短路径的权为 从u到v的最短路径是权 的任何路径 节点V的前驱节点表示为:Vπ 需要说明的是这里讨论的单源最短路径允许出现负数权值,但是不能图中不能出现权值为负数的环路 常用的单源最短路径的解法有两种:Dijkstra算法和bellman_ford算法。 松弛操作 松弛:先测试v到s之间的最短路径是否可以改善,可以则改善。 这是因为单源最短路径和所有节点对的最短路径都是基于松弛操作来实现的,只不过不同的算法采用了不同的松弛次数和顺序。 if v.d>u.d+w(u,v) v.d=u.d+w(u,v) v.π=v } bellman_ford算法 bellman_ford算法可以解决带有负权值的图的单源最短路径
vector<int> G[maxn]; bool done[maxn];//是否用过(永久标号) int d[maxn];//s到各个点的距离 (dist) int p[maxn];//最短路上的一条边
.指定一个源点,求出到其余各个顶点的最短路径,也叫”单源最短路径”. 循环周期: 先确认距离最短的下一跳,再更新下一跳的邻居. 输出: 得到从源点到剩下所有节点的最短路径信息. 然后核心问题就是分别求出v0到v1~v8的最短路径. :”最短路径优先”! 上一跳),然后根据上一跳的递归就能沿最短路径回到v0。
单源最短路径问题描述 给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为源。现在要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。 这个问题通常称为单源最短路径问题 1.无权最短路径(非唯一) 算法分析 由于图没有权,所以我们只需要关注路径上的边 无权最短路径实质上是特殊的有权最短路径,因为我们可以将每条边按权为1处理。 Dijkstra算法是解决有权无负值单源最短路径的经典算法。 图解说明 image.png 核心代码 /** * 著名的dijkstra算法 解决单源最短路径(权无负值) * * @param s * 起点 无权最短路径借助广度优先搜素实现,其时间界限为: O(|E|+|V|) Dijkstra是解决有权无负值图单源最短路径的经典算法。
算法用于求图的多源最短路径(多源最短路径:图的所有顶点到其他顶点的最短路径),时间复杂度和其他求最短路算法相比较高,如果一些题目只要求求单源最短路径(单源最短路径:图的某个顶点到其他顶点的最短路径)的话 ,Floyd算法显然不是最好的选择,那么今天我们来看一下另一个用于求单源最短路径的算法:Dijkstra算法。 图中共有A、B、C、D四个顶点,五条边,假设我们现在要求顶点B到其他顶点的最短路径,依据Dijkstra算法的原理: 首先我们先找到距离顶点B路径最短的顶点,在这个图中很明显距离顶点B路径最短的点为顶点 很明显,B–>D–>C(路径为3)这条路的路径小于B–>C(路径为6)这条路的路径,那么我们更新从顶点B到顶点C的最短路径,顶点D的试探结束。 之后我们继续寻找距离顶点B路径最短并且没有被标记的顶点,现在距离顶点B路径最短并且没有被标记的顶点为顶点C(顶点D已经被标记了),同样的重复“缩放”过程,直到图中所有的顶点都被标记。
void floyd() { for (int k=1; k<=n; ++k) { for (int i=1; i<=n; ++i) { for (int j=1; j<=n; ++j) { if (i == j || i == k || j == k) continue; //避免不必要的判断 提高程序效率 else g[i][j] = min(g[i][j], g[i][k]+g[k][j]); } } } }
单源最短路径问题(Java) 1、问题描述 2、算法思路 3、代码实现 4、算法正确性和计算复杂性 4.1 贪心选择性质 4.2 最优子结构性质 4.3 计算复杂性 5、参考资料 ---- ---- 另外,还给定V中的一个顶点, 称为源。现在要计算从源到所有其他各顶点的最短路长度。这里路的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。 其中,V表示顶点集合,E表示各个节点之间的边。 2、算法思路 对于单源最短路径问题,Dijkstra算法是解决这个问题的贪心算法。 基本思想 设置顶点集合S并不断地做贪心选择来扩充这个集合。 一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。 初始时,S 中仅含有源。 设u是G 的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u 的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。
两个顶点间路径长度最短的那条路径称为两个顶点间的最短路径,其路径长度称为最短路径长度。 最短路径在实际中有重要的应用价值。如用顶点表示城市,边表示两城市之间的道路,边上的权值表示两城市之间的距离。 算法解决的是有带权连通图(带权有向图也可以)中单个源点到其他顶点的最短路径问题,所以也叫作单源最短路径算法。其主要特点是每次迭代时选择的下一个顶点是标记点之外距离源点最近的顶点。 2.3算法基本过程 Dijkstra 算法求解单源最短路径问题的基本步骤如下: (1)设立U 和Y两个节点集合, Y用于保存所有未被访问的节点,U 记录所有已经访问过的节点。 3.5具体实现 Dijkstra算法核心代码: /************************************************** func:求带权有向图的单源最短路径; para: ,输出结果如下: image.png 再求节点0到2的最短路径,输出结果如下: image.png 4.小结 (1)本文实现的Djkstra求单源最短路径,在具体实现上采用邻接矩阵存储图的信息
维护两个集合A和B,A表示已确定最短路径的结点集(红色表示), B表示邻居结点(蓝色表示)。 ? 以此类推,把 B中距离最短的结点2放入A中,加入邻居,然后舍弃更远的(4,7) ? 最后得到起点到其他结点的最短路径 ? return a.dis < dis; } }; int n, m, x, y, z; vector<edge>e[maxn]; //邻接表存图 int dis[maxn], pre[maxn]; //记录最短路径和 前驱节点 bool vis[maxn]; //记录是否已入A,实现舍弃操作 void print_path(int s, int t) { //打印起点s到点t最短路径 if (s == t)return q.empty()) { node cur = q.top(); q.pop(); if (vis[cur.id])continue; vis[cur.id] = true; //即已找到最短路径
Dijkstra-单源最短路径算法 1、算法概述 2、算法实例 3、实战案例 3.1 题目描述 3.2 解题思路与代码实现 1、算法概述 Dijkstra算法用来计算一个点到其他所有点的最短路径的算法 ,是一种单源最短路径算法。 2.u标记为已确定最短路径。 (如果无法到达则输出-1) 输入输出样例 输入 3 3 1 2 1 1 3 5 2 3 2 输出 0 1 3 3.2 解题思路与代码实现 很明显,这是一道求最短路径的题,而且还是单源最短路径 我们还可以输出从节点1开始到其他所有节点的最短路径
7 2 1 2 3 1 2 3 3 4 10 2 5 5 0 4 4 2 2 4 2 5 8 6 4 1 6 6 0 1 5 1
Dijkstra算法用来计算图的单源最短路径,实际上就是两步: 将当前未纳入最短路的符合要求的距离最短结点纳入最短路; 将所有与当前纳入的结点有关联并且未被纳入最短路的结点最短距离进行更新。 Dijkstra算法的分解思路是: 到达某节点的cost最小路径 --(从这里面选)--> { 到达其相邻节点的cost最小路径 } 独一选择性: 只挑选: Min {到达其相邻节点的最短路径} 题目 画出图后发现其实就是一个最短路问题。用Dij解决,自己写了个以猷长为起点的Dij,无限WA,无奈到网上找了篇解题报告。发现向图中添加一个铺助起始点,可以很完美地解决问题。 另外这题中加入了结点等级的限制,我们可以枚举最高等级,依据最高等级,确定能纳入最短路的结点的等级范围。 至此,就完完全全的就是一个最短路了 代码示例 #include<iostream> #include<cmath> using namespace std; const int inf=0x7fffffff
题目描述 如题,给出一个有向图,请输出从某一点出发到所有点的最短路径长度。 输入输出格式 输入格式: 第一行包含三个整数N、M、S,分别表示点的个数、有向边的个数、出发点的编号。 输出格式: 一行,包含N个用空格分隔的整数,其中第i个整数表示从点S出发到点i的最短路径长度(若S=i则最短路径长度为0,若从点S无法到达点i,则最短路径长度为2147483647) 输入输出样例
最短路径: 在一个带权图中,顶点V0到图中任意一个顶点Vi的一条路径所经过边上的权值之和,定义为该路径的带权路径长度,把带权路径最短的那条路径称为最短路径。 DiskStra算法: 求单源最短路径,即求一个顶点到任意顶点的最短路径,其时间复杂度为O(V*V) 如图所示:求顶点0到各顶点之间的最短路径 代码实现: #include<stdio.h> #include printf("∞ "); }else{ printf("%d ",g.arcs[i][j]); } } printf("\n"); } } //Dijkstra算法,求单源最短路径 ,其时间复杂度为O(V*V*V) 如图所示,求<1,0>之间的最短路径: 代码实现: #include<stdio.h> #include<stdlib.h> #define MaxVexNum 50 ;i<n;i++){ for(int j=0;j<n;j++){ A[i][j]=g.arcs[i][j]; path[i][j]=-1; } } //第二步:三重循环,寻找最短路径
引出问题:多源最短路径的问题 暑假,小文准备去一些城市旅游。为了节省经费以及方便计划旅程,小文希望知道任意两个城市之间的最短路径。假如有四个城市八条公路。 我们这时怎么做? 首先想到了两个指定点的最短路径问题,所以进行n2遍深度或者广度优先搜索,既可以得到最终结果,但别的方法呢? 假设现在只允许经过1号顶点,求任意两点间的最短距离。 e[i][1] + e[1][j]) e[i][j] = e[i][1] + e[1][j] } } 这其实是一种“动态规划”的思想,从i顶点到j号顶点只经过前K号点的最短路程 printf("%10d",e[i][j]); } printf("\n"); } return 0; } 通过这种算法可以求出任意两点之间的最短路径
edges.add(e_5_3); Graph g = new Graph(nodes, edges); return g; } 假设从节点1出发,到达其它节点的最短路径 (权重)为: 出发点 目的地 最短路径(权重)和 全路径 1 1 0 1->1 1 2 1 1->2 1 3 1+5 1->2->3 1 4 8 1->4 1 5 1+5+6 1->2->3->5 package (head, 0); //已经计算过的节点 Set<Node> selectedNodes = new HashSet<>(); //从出发点,找出距离最短的节点 ,否则会在环里转来转去,每转一圈,路径合更小,一直循环,转不出来。 如上图,如果从1出发,要计算到节点2的最短路径,每转一圈,总路径反而更短。这种情况下,可以将所有边上的权重加“最大负权重”,将所有边上的权重变成非负值。
因为Dijkstra算法无法 正确计算负权路径的最短路径(详情可看上一节),所以有了Bellman-Ford算法来解决这一问题。 贝尔曼-福特算法 贝尔曼-福特算法(Bellman-Ford)是由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的,求解单源最短路径问题的一种算法。 贝尔曼-福特算法与迪科斯彻算法类似,都以松弛操作为基础,即估计的最短路径值渐渐地被更加准确的值替代,直至得到最优解。 这样的策略使得贝尔曼-福特算法比迪科斯彻算法适用于更多种类的输入 实现过程 从上面介绍我们可以知道,Bellman算法对每一条边采用松弛操作,对于单源最短路径实现来说, 源点到某一顶点所经过的边最多为V 在存在负环的图中,是求不出最短路径的, 因为每次要在这个环上遍历,最短路径就会无限次的变小。