图数据结构中两节点间的最短路径是指两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径。两节点分别为源点(source)和终点(destination),根据源点是否固定将问题细分为单源最短路径问题和多源最短路径问题,根据图中边是否有权值分为无权图和有权图的最短路径问题。
无权图的单源最短路径算法:
void Unweighted ( Vertex S )
{ Enqueue(S, Q);
while(!IsEmpty(Q)){
V = Dequeue(Q);
for ( V 的每个邻接点W )
if ( dist[W]==-1 ) {
dist[W] = dist[V]+1;
path[W] = V;
Enqueue(W, Q);
}
}
}
类似于连通图的遍历算法,从节点S开始遍历并将S加入队列,然后进入循环结构判断队列是否为空,若队列非空就将元素弹出,进入嵌套的内循环,针对每一个与出队节点相邻的节点W,判断其是否访问过,若未访问过就加入队列,具体的C++代码实现如下:
void GraphAdjMat::Unweighted(Vertex s)
{
int dist[20],path[20];
for(int i=0;i<20;i++)
{
dist[i]=-1;
path[i]=-1;
}
dist[s-1]=0;
LinkedQueue<int> NodeQueue;
NodeQueue.AddElement(s);
while(!NodeQueue.IsEmpty())
{
Vertex temp=NodeQueue.DeleteElement();
for(int i=0;i<VertexNum;i++)
{
if(Weight[temp-1][i]!=-1&&dist[i]==-1)
{
dist[i]=dist[temp-1]+1;
path[i]=temp;
NodeQueue.AddElement(i+1);
}
}
}
for(i=0;i<20;i++)
{
std::cout<<dist[i]<<" ";
}
std::cout<<std::endl;
for(i=0;i<20;i++)
{
std::cout<<path[i]<<" ";
}
std::cout<<std::endl;
}
有权图的单源最短路径算法Dijkstra:
void Dijkstra( Vertex s )
{ while (1) {
V = 未收录顶点中dist最小者;
if ( 这样的V不存在)
break;
collected[V] = true;
for ( V 的每个邻接点W )
if ( collected[W] == false )
if ( dist[V]+E<V,W> < dist[W] ) {
dist[W] = dist[V] + E<V,W> ;
path[W] = V;
}
}
} /* 不能解决有负边的情况*/
具体的C++实现程序代码如下:
void GraphAdjMat::Dijkstra(Vertex x)
{
Vertex s=x-1;
Set<int> S;
S.Set_Insert(s);//初始化集合S
Set<int> Q;
bool collected[5];
int dist[5];
int path[5];
for(int i=0;i<5;i++)
{
if(i!=s)
Q.Set_Insert(i);//初始化集合Q
}
for(i=0;i<5;i++)
{
dist[i]=100;//初始化为无穷大
path[i]=-1;
collected[i]=false;//标志该点为被收录
if(Weight[s][i]!=-1)//若该点与s相邻,则dist赋值为权值
{
dist[i]=Weight[s][i];
path[i]=s;
}
}
dist[s]=0;
int v=s;//表示新加入的节点
while(1)
{
if(Q.GetSize()==0)
break;
int mindist=1000;
int temp=-1;
//提取Q中distance最小的点u,加入到S中
for(int i=0;i<Q.GetSize();i++)
{
if(dist[Q[i]]<mindist)
{
mindist=dist[Q[i]];
temp=i;
}
}
if(temp==-1)
temp=0;
v=Q[temp];
collected[v]=true;
S.Set_Insert(v);
Q.Set_Remove(v);
for(i=0;i<Q.GetSize();i++)
{
if(Weight[v][Q[i]]!=-1)//若两点相邻,就计算并更新dist值
{
if(collected[Q[i]]==false&&dist[v]+Weight[v][Q[i]]<dist[Q[i]])
{
dist[Q[i]]=dist[v]+Weight[v][Q[i]];
path[Q[i]]=v;
}
}
}
}
for(i=0;i<5;i++)
{
std::cout<<dist[i]<<" ";
}
std::cout<<std::endl;
for(i=0;i<5;i++)
{
std::cout<<path[i]+1<<" ";
}
std::cout<<std::endl;
}
总结一下,Dijkstra算法利用两个集合S和Q,其中S为从源点开始不断加入的最小dist值的节点集合,Q为还没有加入的节点集合,加入的过程采用贪心算法,即每次计算上次新加入的节点的临接点,计算求得最小dist的临接点加入到S中,利用collect布尔数组变量判断是否已经加入,同时在加入的过程中保存其前一个节点v,从而能够在后续的路径寻找过程中确定节点序列。