最短路径Djikstra算法 模板+ 例题(九度acm1341: 艾薇儿的演唱会)

最短路径Djikstra算法:

主要思想:先找确定的(即当前最短的),不确定的则动态更新

从(V-S)集合中找v0到其中最短的路径的顶点,加入到S中并更新(V-S)集合中其他顶点的路径值。

―――――――

参考模板:

Dp初值为v0到某个顶点v的权值weight[ v0][ v],S初值全为false,pre初值全为-1.(Dp,S,pre下标都从1开始)
Dp[ v0]=0;//此行也可以不要,因为下面用Dp[]时总会判断!S[],而S[v0]=TRUE,故Dp[ v0]永远不会被用到

S[ v0]=TRUE;//v0顶点属于S集合
for( int i= 1; i< n; ++i) //注意,下面的循环只需n-1次,将除v0之外的其他所有顶点逐个加入S集合(实际循环时,循环次数可能小于n-1,因为如果在(V-S)集合中找不到从v0可达的点,则会直接跳出:if ( minIndex == -1 )  break)。
 {   min= INFINITY;           

     minIndex= -1;
    for( int k= 1; k<= n; ++k) //求(V-S)集合内与v0距离最近的顶点(Dp[]最小)

     {  if( !S[k] &&min>Dp[k])

        {  minIndex= k;

            min= Dp[ k];

        } 

     }

     if ( minIndex == -1 ){  break;}//此时 (V-S)集合中所有的Dp[]都是无穷大,则表明v0到这些顶点都不可达,故后面的不用再判断了,直接跳出循环

else//选中一个最短且可达点(即Dp[]最小,且不是无穷大)

{  S[ minIndex]= TRUE;//离v0最近的顶点minIndex加入到S集合

   for(int k= 1; k<= n; ++k)    //更新当前最短路径及距离
     {  if( !S[ k] && weight[ minIndex][ k]< INFINITY && Dp[ k]> min+  weight[ minIndex][ k])    //一定要判断weight[ minIndex][ k]< INFINITY,

//否则后面的min+  weight[ minIndex][ k]可能会超出整数的范围而越界,其实应该用减法,因为用加法可能超出整数的范围,if里面只需2个条件即可:if ( !S[ k] && Dp[ k]- min> weight[ minIndex][ k] ) 但这样会不好理解,所以还是可以用上面的3个条件的。       

            {  Dp[ k]= min+ weight[ minIndex][ k];  //修改v0到(V-S)集合中其他顶点的路径长度
                     pre[ k]= minIndex;//minIndex作为桥梁(或中间人),vo与k变得更短,pre记录前一个,以便输出最短路径经过的顶点路径信息(如“1->3->4->2”)
                }
         }

}
 }

―――――

伪代码参考:

Dp[]权值,S[]全false,pre全-1,下标都从1开始

Dp[ v0]=0;S[ v0]=T;

for( i= 1; i< n; ++i)

 {   min= 无穷大; minIndex= -1;

     for( k= 1; k<= n; ++k) //找最短

     {  if( !S[k] && min>Dp[k])

        {  minIndex= k;

            min= Dp[ k];

        } 

     }

     if ( minIndex == -1 ) break;

else

{  S[ minIndex]= T;

   for( k= 1; k<= n; ++k)    //更新

     {  if( !S[ k] && weight[ minIndex][ k]< INFINITY &&

                Dp[ k]> min+  weight[ minIndex][ k])             

            {  Dp[ k]= min+ weight[ minIndex][ k]; 

               pre[ k]= minIndex;

             }

         }

}

}

 

--------------------

加入最短的顶点(dp[]最小)后,更新其他顶点的dp[],如下图:

论证:每次从(V-S)集合中找v0到其中最短的路径的顶点v(即Dp[v]最小),Dp[v]为从v0到v的最短路径长度。从v0到v,还有比Dp[v]更短的路径吗?没有了。因为如果还有更短的,则必是经过其他某点vt(属于(V-S)),使Dp[v]> Dp[vt]+ weight[ vt][v],而Djikstra算法要求权值为正,所以Dp[v]> Dp[vt],即Dp[v]不是从v0到(V-S)中路径最短的顶点,而路径最短的顶点应该是vt,这与假设(Dp[v]最小)矛盾。

ACM例题:

http://ac.jobdu.com/problem.php?id=1341

九度acm1341: 艾薇儿的演唱会(已AC)

#include <iostream>

#include <memory.h>

#include <algorithm>

#include <vector>

using namespace std;

 

const int MAX_NUM_VERTICES= 101;//最大的节点个数

//const int MAX_NUM_EDGES= 1001;//最大的边个数

const int MAX_INT= 1000000;//最大的整数

 

//查询最短路径并输出

void SearchPathAndPrint( int* pre,  int v0, int v1)

{

       vector< int> viShortestPath;//最短路径的向量

       viShortestPath.push_back( v1);//加入终点

       int iCurr= pre[ v1];

       while( v0 != iCurr)

       {

              viShortestPath.push_back( iCurr);

              iCurr= pre[ iCurr];

       }

       viShortestPath.push_back( v0);//加入起点

 

       输出

       //cout<< v0;

       从某个终点v1开始,根据pre[]从后往前搜索,直到找到起始点v0

       //for ( int i= ( int) viShortestPath.size()- 2; i>= 0 ; --i)

       //{

       //     cout<< "->"<< viShortestPath[ i];

       //}

       //cout<< endl;

}

 

int main()

{

       int n;//节点个数(节点下标从1开始)

       cin>> n;

      

       int iWeight[ MAX_NUM_VERTICES][ MAX_NUM_VERTICES];//一点到另一点的权值

       //memset( iWeight, MAX_INT, MAX_NUM_VERTICES* MAX_NUM_VERTICES* sizeof( int));//初始值为无穷大

 

       for ( int i= 1; i<= n; ++i)

       {

              for ( int j= 1; j<= n; ++j)

              {

                     iWeight[ i][ j]= MAX_INT;

              }

       }

       int iNumEdges;//边的个数

       cin>> iNumEdges;

 

       int v0, vEnd;

       cin>> v0>> vEnd;

 

       int iv1, iv2, iWght;

       for ( int i= 0; i< iNumEdges; ++i)//输入每条边的信息(起点索引,终点索引,边的权值)

       {

              cin>> iv1>> iv2>> iWght;

              if ( iWeight[ iv1][ iv2] > iWght)

              {

                     iWeight[ iv1][ iv2]= iWeight[ iv2][ iv1]= iWght;

              }                  

       }

 

       int iShortestLenFromStart[ MAX_NUM_VERTICES];//连通分量的数组

       bool abSetOK[ MAX_NUM_VERTICES];//已经加入到最短路径组的集合

       int pre[ MAX_NUM_VERTICES];

       /*最短路径中上一个的索引(注:输出时要倒着来,我开始想到用next,但不行,

       因为树的任一节点A只能有一个父节点,但A可同时有几个孩子)*/

      

       memset( iShortestLenFromStart, 0, MAX_NUM_VERTICES* sizeof( int));//初始值为无穷大

       for ( int i= 1; i<= n; ++i)//输入每条边的信息(起点索引,终点索引,边的权值)

       {

              iShortestLenFromStart[ i]= iWeight[ v0][ i];//初始值为权值                         

       }

       //iShortestLenFromStart[ v0]= 0;

      

 

       memset( abSetOK, false, MAX_NUM_VERTICES* sizeof( bool));//初始值为无穷大

       abSetOK[ v0]= true;

 

       memset( pre, -1, MAX_NUM_VERTICES* sizeof( int));//初始值为无穷大

                    

       int iLastInserted= v0;

       int iOutOfSetMin, iOutOfSetMinIndex;//在已加入到最短路径组外的最小值

       for ( int i= 1; i< n; ++i)

       {

              iOutOfSetMin= MAX_INT;

              iOutOfSetMinIndex= -1;

              for ( int w= 1; w<= n; ++w)

              {

                     if ( !abSetOK[ w] && iShortestLenFromStart[ w]< iOutOfSetMin)

                     {

                            iOutOfSetMin= iShortestLenFromStart[ w];

                            iOutOfSetMinIndex= w;

 

                     }//if ( !abSetOK[ w]

              }//for ( int w         

             

              if ( iOutOfSetMinIndex == -1)//如==-1则表明剩下的是孤立的不可达点(即不连通),

//此时 (V-S)集合中所有的Dp[]都是无穷大,故后面的不用再判断了,直接跳出循环

              {

                     break;

              }

              else

              {

                     abSetOK[ iOutOfSetMinIndex]= true;

                     pre[ iOutOfSetMinIndex]= iLastInserted;

                     iLastInserted= iOutOfSetMinIndex;

 

                     for ( int w= 1; w<= n; ++w)

                     {

                            if ( !abSetOK[ w] && iWeight[ iOutOfSetMinIndex][ w]< MAX_INT

                                   && iWeight[ iOutOfSetMinIndex][ w]+ iOutOfSetMin < iShortestLenFromStart[ w])

                            {

                                   iShortestLenFromStart[ w]= iWeight[ iOutOfSetMinIndex][ w]+ iOutOfSetMin;

                                   pre[ w]= iOutOfSetMinIndex;

 

                            }//if ( !abSetOK[ w]

                     }//for ( int w

                    

              }//if ( iOutOfSetMinIndex                  

             

       }//for ( int i

 

       //cout<<"最短的路径为: ";

       SearchPathAndPrint( pre, v0, vEnd);//查询最短路径并输出

 

       cout<< iShortestLenFromStart[ vEnd]<< endl;

 

       //system("pause");

       return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值