Dijkstra最短路径问题求解

Dijkstra(迪科斯彻)无负权值最短路径问题。此方法可以计算有向图,无向图中任意两结点间最短路径,但路径的权不能为负。

Dijkstra最短路径算法又分顺向和逆向。比如计算某图中结点a到结点b的最短路径,顺向Dijkstra算法是从起点结点a开始延伸,一直到终点结点b;而逆向Dijkstra算法是从终点结点b开始延伸,一直到起点结点a。本文只介绍顺向Dijkstra算法,也是最常用的。

首先定义几个概念,方便后续的描述:

如果选择从结点i向其所有相邻可达的结点延伸,则称结点i为延伸点;没有充当过延伸点的结点称为可变标记点,充当过延伸点的结点称为固定标记点。延伸点只从可变标记点中选取。

假设取结点1为源点,用(i,kj)来标记结点j,其中i表示是从结点i延伸到j,kj表示从源点1到结点j的路径长度,且此时j为可变标记点。若结点j是固定标记点,则使用[i,kj]来标记。
假设图中有n个结点,结点1为源点,结点n为汇点,计算从结点1到结点n的最短路径。
Dijkstra算法步骤:
1、给图中结点j(j=1,2,3,...,n)一个可变标记(1,kj),1表示从结点1延伸到j,kj表示路径长度,j=1时,kj=0,j=2,3,...,n时,kj=∞。
2、选择所有可变标记点中kj最小的结点x。若结点j不存在,则运算结束;否则转到3。
3、对于结点x相邻可达的每一个非固定标记点y,若kx+dxy<ky,则把结点y的标记修改为(x,kx+dxy);否则不修改。
4、将结点x的标记修改成固定标记,转到2。

如图1,计算结点1到结点7的最短路径长度及走向。

首先加上初始可变标记,如图2。

所有可变标记中,结点1标记的长度最小为0,所以从结点1向其相邻可达的可变标记点2和3延伸,延伸到结点2的距离为0+5=5<∞,则修改结点2的标记为(1,5),延伸到结点3的距离为0+9=9<∞,则修改结点3的标记为(1,9),修改结点1的标记为固定标记[1,0],如图3。

此时,所有可变标记中,结点2标记的长度最小为5,所以从结点2向其相邻可达的可变标记5延伸,延伸到结点5的距离为5+12=17<∞,修改结点5的标记为(2,17),修改结点2的标记为固定标记[1,5],如图4。

此时,所有可变标记中,结点3标记的长度最小为9,所以从结点3向其相邻可达的可变标记4和6延伸,延伸到结点4的距离为9+15=24<∞,修改结点4的标记为(3,24),延伸到结点6的距离为9+23=32<∞,修改结点6的标记为(3,32),修改结点3的标记为固定标记[1,9],如图5。

此时,所有可变标记中,结点5标记的长度最小为17,所以从结点5向其相邻可达的可变标记4和7延伸,延伸到结点4的距离为17+5=22<24,修改结点4的标记为(5,22),延伸到结点7的距离为17+14=31<∞,修改结点7的标记为(5,31),修改结点5的标记为固定标记[2,17],如图6。

此时,所有可变标记中,结点4标记的长度最小为22,所以从结点4向其相邻可达的可变标记6和7延伸,延伸到结点6的距离为22+8=30<32,修改结点6的标记为(4,30),延伸到结点7的距离为22+7=29<31,修改结点7的标记为(4,29),修改结点4的标记为固定标记[5,22],如图7。

此时,所有可变标记中,结点7标记的长度最小为29,但结点7无相邻可达的可变标记点,修改结点7的标记为固定标记[4,29]。此时,结点6是唯一的可变标记,修改为固定标记[4,30]。至此,运算结束,最终结果如图8。

得到结点1到结点7的最短路径长度为29,倒退找出路径走向为:7<=4<=5<=2<=1。同时也得到了结点1到其他各结点的最短路径:6<=4<=5<=2<=1,长度为30;5<=2<=1,长度为17;4<=5<=2<=1,长度为22;3<=1,长度为9;2<=1,长度为5。
在此算法中,如果计算结点1到结点4的最短路径,一旦结点4成为固定标记点,即可结束运算,得到最短路径长度及走向,以提高计算效率。有了这样一个思路,代码就easy了。

代码的思路与算法步骤类似,需要输入结点数N和边信息矩阵d,且d[i][j]中有规定,若结点i,j直接可达,则d[i][j]为权值;若i等于j,则d[i][j]为0;若i,j不直接可达,则d[i][j]为无穷大(可以定义一个足够大数,充当无穷大,但最好不要使用int型数据所能表示的最大数)。

为编程方便,结点的下标从0开始,本代码只是针对本例题的,即路径的起点结点是1,提供本代码旨在抛砖引玉,在本代码的基础上稍作修改,即可实现计算任何两结点之间的最短路径。

#include<iostream>
using namespace std;
#define H 1000000
class NODEtag//标记类
{
public:
	NODEtag():tag(0){}
	int node;//延伸结点
	int length;//长度
	int tag;//tag == 0表示可变标记,tag == 1表示固定标记
};
int getminlength(NODEtag *nodetag,int n)//寻找可变标记中长度最小的结点
{
	int i,len = H,t = -1;
	for(i=0;i<n;i++)
	{
		if(nodetag[i].tag == 0&&nodetag[i].length < len)
		{
			len = nodetag[i].length;
			t = i;
		}
	}
	return t;
}
void shortpath(int **d,int N,NODEtag *nodetag)//计算最短路径
{
	int i,t,n = N;
	while(n--)
	{
		t = getminlength(nodetag,N);//获取当前可变标记结点中,长度最短的结点
		for(i=0;i<N;i++)
		{
			if(d[t][i] != -1&&nodetag[i].tag == 0&&t != i)//相邻可达的可变标记点
			{
				if(nodetag[t].length + d[t][i] < nodetag[i].length)
				{
					nodetag[i].node = t;
					nodetag[i].length = nodetag[t].length + d[t][i];
				}
			}
		}
		nodetag[t].tag = 1;
	}
}
void getpath(NODEtag *nodetag,int n)//获取路径走向,从终点开始以倒退方式找出路径
{
	cout<<n+1;
	while(n!=0)
	{
		n = nodetag[n].node;
		cout<<" <= "<<n+1;
	}
	cout<<endl;
}
int main()
{
	int **d,N,i,j;
	NODEtag *nodetag;
	cout<<"输入结点数: ";
	cin>>N;
	d = new int*[N];
	cout<<"输入边信息矩阵:"<<endl;
	for(i=0;i<N;i++)
	{
		d[i] = new int[N];
		for(j=0;j<N;j++)
			cin>>d[i][j];
	}
	nodetag = new NODEtag[N];
	for(i=0;i<N;i++)//结点标记初始化
	{
		nodetag[i].node = 0;
		nodetag[i].length = (i==0?0:H);
		nodetag[i].tag = 0;
	}
	shortpath(d,N,nodetag);//计算最短路径
	cout<<endl<<"结点1到其余结点的最短路径信息:"<<endl;
	for(i=1;i<N;i++)//输出最短路径长度,及走向
	{
		cout<<"结点1到"<<i+1<<"长度为:"<<nodetag[i].length<<",走向为:";
		getpath(nodetag,i);
	}
	for(i=0;i<N;i++)//释放内存
		delete[] d[i];
	delete[] d;
	delete[] nodetag;
	return 0;
}

程序运行截图如下,所得结果与之前分析一直。本程序中对2结点间不可达的情况未做处理,大家可以自己补上!

转帖请注明出处:http://blog.csdn.net/love_feng_forever/article/details/21301875




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值