六、最短路径——迪杰斯特拉(Dijkstra)算法

        在网图和非网图中,最短路径的含义是不同的。由于非网图它没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径;而对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。显然,我们研究网图更有实际意义,就地图来说,距离就是两顶点间的权值之和。而非网图完全可以理解为所有的边的权值都为1的网。

        迪杰斯特拉是一个按路径长度递增的次序产生最短路径的算法。它的思路大体是这样的。

        比如说要求下图中顶点v0到顶点v1的最短距离,没有比这更简单的了,答案就是1,路径就是直接vo连线到v1。

         由于顶点v1还与v2、v3、v4连线,所以此时我们同时求得了vo→v1→v2=1+3=4,
v0→v1→v3=1+7=8,v0→v1→v4=1+5=6。
        现在,我问v0到v2的最短距离,如果你不假思索地说是5,那就犯错了。因为边上都有权值,刚才已经有v0→v1→v2的结果是4,比5还要小1个单位,它才是最短距离。

        由于顶点v2还与v4、v5连线,所以此时我们同时求得了v0→v2→v4其实就是v0→v1→v2→v4=4+1=5。这里v0→v2我们用的是刚才计算出来的较小的4。此时我们也发现v0→v1→v2→v4=5要比vo→v1→v4=6还要小。所以vo到v4目前的最小距离是5。

         当我们要求v0到v3的最短距离时,通向v3的三条边,除了v6没有研究过外,

v0→v1→v3的结果是8,而v0→v4→v3=5+2=7。因此,v0到v3的最短距离是7。

        好了,我想你大致明白,这个迪杰斯特拉(Dijkstra)算法是如何干活的了。它并不是一下子就求出了v0到v8的最短路径,而是一步步求出它们之间顶点的最短路径,过程中都是基于已经求出的最短路径的基础上,求得更远顶点的最短路径,最终得到你要的结果。

        现有如下一个网,和它的邻接矩阵

        现在我们看一下如何求出v0到v8的最短路径

#include<iostream>
#include<vector>
#include<queue>
using namespace std;

#define MAXVEX 100//最大顶点数
typedef char VertexType;//顶点类型
typedef int EdgeType;//边上的权值类型
typedef struct
{
	VertexType vexs[MAXVEX];//顶点表
	EdgeType arc[MAXVEX][MAXVEX];//邻接矩阵
	int numVertexte;//当前顶点数
	int numEdges;//当前边数
}MGraph;

void ShortestPath_Dijkstra(MGraph G, int v0, vector<int>& P, vector<int>& D)
{
	//P[i]的值为顶点i的前驱顶点
	//D[i]的值为v0到i的最短路径长度和
	vector<int> final(G.numVertexte);//final[i]=1表示已求得顶点v0到顶点i的最短路径
	for (int i = 0; i < G.numVertexte; ++i)//初始化数据
	{
		final[i] = 0;//全部初始化为未知最短路径状态
		P[i] = 0;
		D[i] = G.arc[v0][i];
	}
	D[v0] = 0;//v0至v0的路径为0
	final[v0] = 1;//v0至v0不需要求路径

	/*开始主循环,每次求得v0到某个顶点的最短路径*/
	for (int i = 1; i < G.numVertexte; ++i)
	{
		int min = INT_MAX;//当前所知离v0顶点的最近距离
		int k;
		for (int w = 0; w < G.numVertexte; ++w)//寻找离v0最近的顶点
		{
			if (!final[w] && D[w] < min)//如果顶点w为加入最短路径,且v0到w的距离小于之前的min
			{
				k = w;
				min = D[w];
			}
		}
		final[k] = 1;//将目前找到的最近的顶点加入最短路径
		for (int w = 0; w < G.numVertexte; ++w)//修正当前最短路径及距离
		{
			/*此时的min就是前面找到的离v0顶点的最近距离,也就是v0~k的距离*/
			if (!final[w] && (min + G.arc[k][w] < D[w]))
			{//如果顶点w还没加入最短路径且从v0->k->w的距离 小于 直接从v0->w的距离
				D[w] = min + G.arc[k][w];//那么就更新v0->w的距离为 v0->k->w的距离
				P[w] = k;//那么顶点w的前驱顶点就是k
			}
		}
	}
}

        final数组是为了v0到某顶点是否已经求得最短路径的标记,如果v0到vi已经有结果,则final[i]=1。

最终的结果

        其实最终返回的数组D和数组P,是可以得到v0到任意一个顶点的最短路径和路径长度的。例如v0到v8的最短路径并没有经过v5,但我们已经知道v0到v5的最短路径了。由D[5]=8可知它的路径长度为8,由P[5]=4可知v5的前驱顶点是v4,所以v0到v5的最短路径是V0→V1→V2→V4→V5。

        也就是说,我们通过迪杰斯特拉(Dijkstra)算法解决了从某个源点到其余各顶点的最短路径问题。从循环嵌套可以很容易得到此算法的时间复杂度为O(n^{2}),尽管有同学觉得,可不可以只找到从源点到某一个特定终点的最短路径,其实这个问题和求源点到其他所有顶点的最短路径一样复杂,时间复杂度依然是O(n^{2})

        这就好比,你吃了七个包子终于算是吃饱了,就感觉很不划算,前六个包子白吃了,应该直接吃第七个包子,于是你就去寻找可以吃一个就能饱肚子的包子,能够满足你的要求最终结果只能有一个,那就是用七个包子的面粉和馅做的一个大包子。这种只关注结果而忽略过程的思想是非常不可取的。 

        可如果我们还需要知道如v3到v5、v1到v7这样的任一顶点到其余所有顶点的最短路径怎么办呢?此时简单的办法就是对每个顶点当作源点运行一次迪杰斯特拉(Dijkstra)算法,等于在原有算法的基础上,再来一次循环,此时整个算法的时间复杂度就成了O(n^{3})

  • 20
    点赞
  • 92
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
迪杰斯特拉算法(Dijkstra)是一种贪心算法,用于解决最短路径问题。它可以处理有权有向或无向,但不允许有负权边(权重必须为非负数)。 算法思路: 1. 从起点开始,初始化所有节点的距离为无穷大,起点距离为0; 2. 将起点加入“已访问”集合; 3. 对于起点的所有邻居节点,更新它们的距离(如果通过当前节点到达邻居节点的距离小于邻居节点原有的距离,则更新邻居节点的距离); 4. 从未访问集合中选择距离起点最近的节点,加入“已访问”集合; 5. 重复步骤3和4,直到所有节点都被加入“已访问”集合或者没有与起点相连的节点。 算法实现: Dijkstra算法的实现通常使用优先队列(PriorityQueue)来维护未访问集合中距离起点最近的节点。具体实现步骤如下: 1. 创建一个空的优先队列Q,将起点加入Q中,并设置起点到自身的距离为0; 2. 创建一个数组dist[],用于保存起点到各个节点的距离,初始化为无穷大; 3. 创建一个数组visited[],用于标记节点是否被访问过,初始化为false; 4. 将dist[起点]的值设置为0; 5. 当Q不为空时,重复以下步骤: a. 从Q中取出距离起点最近的节点u; b. 如果节点u已经被访问过,则跳过此次循环; c. 将节点u标记为已访问; d. 对于节点u的每个邻居节点v,如果节点v未被访问过并且通过节点u到达节点v的距离小于dist[v],则更新dist[v]的值; e. 将节点v加入Q中。 6. 最终,dist数组中保存的就是起点到各个节点的最短距离。 Dijkstra算法的时间复杂度为O(ElogV),其中E为边数,V为节点数。这是因为算法需要对每个节点的所有邻居节点进行遍历,而优先队列的插入和删除操作的时间复杂度为O(logV)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值