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