[C++]加权有向图 求一个顶点到其他各个顶点的最小加权路径(Dijkstra算法)

最小加权路径:加权有向图两顶点之间权值之和最小的路径(以下称为最短路径)。
注意到一个事实:如果已知从顶点A到B的最短路径,而这条路径经过顶点C,则从A到C的最短路径一定在这条最短路径上(反证可知)。进一步,如果C是从A到B最短路径上经过的倒数第二个顶点,即经过C的下一个顶点是B,那么AB最短路径=AC最短路径+CB。
基于以上事实,如果从源顶点A到目标顶点B1、B2、B3……Bn的最短路径都已知,现在加入一个新目标顶点B(n+1),那么从A到B(n+1)的最短路径是[AB1最短路径+B1B(n+1)]、[AB2最短路径+B2B(n+1)]……[ABn最短路径+BnB(n+1)]以及AB(n+1)中最小的一个。

算法实现

基本定义:
该加权有向图有n个顶点,编号为1~n。(以下数组中下标为0的位置均无实际意义)
有向边使用邻接数组a[n+1][n+1]存储,其所存数值大小代表有向边的权重(有向边的权重均为正数),当边不存在时,数组中对应位置存储的数值是noEdge。
源顶点:sourceVertex。
目的顶点:targetVertex。
数组distanceFromSource[n+1]用于记录源顶点到所有顶点的最短路径长度。初始化时暂时存放源顶点到其他所有顶点的有向边距离,有向边不存在时存放noEdge。
数组predecessor[n+1]用于记录最短路径上到达各个顶点之前的顶点。通过这个数组,可以从目的顶点回溯到源顶点,从而确定最短路径上经过的所有顶点。初始化时源顶点的所有邻接顶点存放源顶点,源顶点存放0,其他顶点存放-1。
链表newReachableVertices用于临时存放最短路径刚刚发生了变化的顶点,因为这些顶点的最短路径发生了变化,所以他们所有的邻接顶点都要重新确定最短路径。初始化时将源顶点的所有邻接顶点存入其中。

开始循环,直到链表newReachableVertices为空:
1.先找出链表newReachableVertices中目前distanceFromSource最小的顶点,记为minDistanceVertex,并从链表中删除该顶点。(因为newReachableVertices中的其他任何顶点都不可能影响到这个顶点)
2.然后遍历该顶点的所有邻接顶点,判断它们的distanceFromSource是否要发生修改,即是否要替换成源顶点到minDistanceVertex最短路径加上minDistanceVertex到其邻接顶点的距离。如果发生替换,则将该邻接顶点的predecessor修改成minDistanceVertex(因为此时从源顶点到该顶点的最短路径经过了minDistanceVertex),并且若该邻接顶点不在链表newReachableVertices中,则将其加入链表中(因为该顶点的最短路径已经发生了变化)。

循环结束之后distanceFromSource中的数据就是从源顶点到各个顶点的最小路径长度,若distanceFromSource[targetVertex]仍为noEdge,说明源顶点到目的顶点并不连通。而从predecessor中,可以从目的顶点找回到源顶点,从而确定这条最短路径上所经过的所有顶点。

代码

这个函数属于加权有向图类adjacencyWDigraph,此处不做详细说明。
关于类adjacencyWDigraph的完整设计,请见有向图(邻接数组描述)

void Dijkstra(int sourceVertex, int targetVertex)//Dijkstra算法计算两顶点之间最小加权路径
	{
		//以下为初始化,可简化
		T* distanceFromSource = new T[n + 1];//用于记录每一个顶点与源顶点的加权最小总距离
		for (int i = 0; i <= n; i++)//初始化,暂定源顶点到每个顶点有向边的加权距离(若源顶点与某定点不存在有向边,则这段距离是noEdge)
			distanceFromSource[i] = a[sourceVertex][i];

		int* predecessor = new int[n + 1];//用于记录到达该顶点之前经过的顶点是谁
		for (int i = 0; i <= n; i++)//初始化,源顶点的前一个顶点是0,源顶点所有邻接顶点的前一个顶点暂定为源顶点,其他顶点的前一个顶点暂定为-1
			predecessor[i] = -1;
		predecessor[sourceVertex] = 0;
		for (int i = 1; i <= n; i++)
			if (a[sourceVertex][i] != noEdge)
				predecessor[i] = sourceVertex;

		graphChain<int> newReachableVertices;//用于临时储存自身与源顶点最短路径发生了改变的顶点
		for (int i = 1; i <= n; i++)//初始化,先将源顶点的所有邻接顶点存入其中
			if (predecessor[i] > 0)
				newReachableVertices.insert(i);

		//以下开始计算源顶点到所有顶点的最小加权路径
		while (!newReachableVertices.empty())
		{
			//首先找到在newReachableVertices中找出目前与源顶点最近的顶点,然后将其从链表中取出
			chainNode<int>* currentNode = newReachableVertices.firstNode;
			int minDistanceVertex = currentNode->element;//minDistanceVertex表示链表中与源顶点最近的顶点
			while (currentNode->next != NULL)//遍历一遍链表,确定最小值
			{
				currentNode = currentNode->next;
				int temp = currentNode->element;
				if (distanceFromSource[minDistanceVertex] > distanceFromSource[temp])
					minDistanceVertex = temp;
			}
			newReachableVertices.erase(minDistanceVertex);//将其从链表中移除

			for (int i = 1; i <= n; i++)//遍历minDistanceVertex的除了源顶点以外的所有邻接顶点,判断他们与源顶点的最小距离是否要发生改变,如果发生了改变则修改该顶点的predecessor,并将该顶点加入链表中(如果之前不存在于链表中)
			{
				if (a[minDistanceVertex][i] != noEdge&&i!=sourceVertex)
				{
					if (distanceFromSource[i] == noEdge || distanceFromSource[i] > distanceFromSource[minDistanceVertex] + a[minDistanceVertex][i])
					{
						distanceFromSource[i] = distanceFromSource[minDistanceVertex] + a[minDistanceVertex][i];
						predecessor[i] = minDistanceVertex;
						if (newReachableVertices.indexOf(i) == -1)
							newReachableVertices.insert(i);
					}
				}
			}
		}

		//然后输出需要的最小加权路径信息
		if (distanceFromSource[targetVertex] != noEdge)//源顶点与目标顶点连通
		{
			stack<int> stack;//利用栈,从终点回溯到源顶点
			stack.push(targetVertex);
			int temp = predecessor[targetVertex];
			while (temp != 0)//一直找到源顶点
			{
				stack.push(temp);
				temp = predecessor[temp];
			}
			cout << "最小加权路径经过的顶点:";
			while (!stack.empty())
			{
				cout << stack.top()<<" ";
				stack.pop();
			}
			cout << "\n最小加权路径长度:" << distanceFromSource[targetVertex]<<endl;
		}
		else
		{
			cout << "两顶点不连通" << endl;
		}

	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值