描述
给定一个带权有向图 G=(V,E) ,其中每条边的权是一个非负实数。另外,还给定 V 中的一个顶点,称为源。现在我们要计算从源到所有其他各顶点的最短路径长度。这里的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。
解法
将图G中所有的顶点V分成两个顶点集合S和T。以v为源点已经确定了最短路径的终点并入S集合中,S初始时只含顶点v,T则是尚未确定到源点v最短路径的顶点集合。具体的操作是不断的遍历未标记的点(V-S)(其要么直接从源点出发,要么经过S中的点),找出其离源点最近的点,然后将其加入S,并更新经过该点而比不经过该点权值小的顶点的信息。直到遍历完其他所有顶点。
代码
#include "stdio.h" #include "string.h" #define GMAP_LEN 6 #define INFINITY 10000 int GMAP[GMAP_LEN][GMAP_LEN]={ {INFINITY,INFINITY,10,INFINITY,30,100}, {INFINITY,INFINITY, 5,INFINITY,INFINITY,INFINITY}, {INFINITY,INFINITY,INFINITY,50,INFINITY,INFINITY}, {INFINITY,INFINITY,INFINITY,INFINITY,INFINITY,10}, {INFINITY,INFINITY,INFINITY,20,INFINITY,60}, {INFINITY,INFINITY,INFINITY,INFINITY,INFINITY,INFINITY} }; int final[GMAP_LEN]; int path[GMAP_LEN][GMAP_LEN]; int result[GMAP_LEN]; /** * 单源最短路径 * v:源点 * len:存储图邻的接矩阵长度 即顶点数 * GMAP:存储图的邻接矩阵 * final:标志 是否已找到v到该顶点的最短路径 1:已找到 ; 0:没找到 * path:path[i] v到顶点i的最短路径 若path[i][j]==1,则顶点j是v到i最短路径上的顶点 * result:结果 即最短的路径长度(权值) */ void shortestPath_DIJ(int v,int len){ //minValue 未标记为已找到点中离源点v最近的点的权值 //minIndex 未标记为已找到点中离源点v最近的点 int i,j,minValue,minIndex; for(i=0;i<len;i++){ final[i]=0; result[i]=GMAP[v][i]; //初始化结果权值 if(result[i]<INFINITY){ path[i][v]=1;//源点 path[i][i]=1;//终点 } } result[v]=0; //到顶点v本身的权值为0 final[v]=1; //源点v为已找到的点 for(i=1;i<len;i++){ minValue=INFINITY; for(j=0;j<len;j++)//找到从原点开始到所有其能到达点(已经找到) 的路径中最近的点 if(!final[j] && result[j]<minValue){//顶点j为未找到结果的点 minValue=result[j]; minIndex=j; } final[minIndex]=1;//离v最近点的点 标志为已找到 for(j=0;j<len;j++){ if(!final[j] && (minValue+GMAP[minIndex][j])<result[j]){ //若是经过minIndex点会更近 则更新路径和权值 result[j]= minValue+GMAP[minIndex][j]; path[j]=path[minIndex]; //更新路径 经过该点故复制从原点到minIndex的路劲 path[j][j]=1;//并设置当前顶点为该次寻找路径的终点 } } } } int main(){ int i,j,len=6; shortestPath_DIJ(0,len); for(i=1;i<len;i++){ if(result[i]>=INFINITY){ printf("%d到%d:走不通!\n\n",0,i); continue; } printf("%d到%d:%d\n",0,i,result[i]); printf("经过的顶点:0"); for(j=1;j<len;j++) if(path[i][j])printf(" - %d",j); printf("\n\n"); } return 0; }
题目推荐:
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=298
题意:
给出一些关键牌和关键牌间的连接情况, 开始时推倒1号关键牌,判断最后倒下的牌是关键牌,还是两张关键牌之间的普通牌,并输出最后倒下的牌的牌号或该行牌的两端的关键牌牌号。
分析:
单源最短路算法, 即把倒下所需的时间看作是路径长度. 判断最后倒下的是关键牌还是普通牌. 可分两种情况考虑: ①最后倒下的牌是关键牌, 则其时间和位置就是1号关键牌到其它所有关键牌的最短路径的最大值所对应的那个关键牌, 该时间记为max1; ②若最后倒下的是两关键牌之间的某张普通牌, 则可推导出公式: (time[i]+time[j]+edge[i][j])/2.0 , 其中time[i]为i号关键牌倒下时所在的时间点, 该公式的得数即为该行牌完全倒下时所在的时间点. 求出所有行完全倒下时间的最大值max2. 最后, 若max1<max2, 即为上面所述的情况②; 否则, 就是情况①;
代码:
#include "stdio.h" #include "string.h" #define GMAP_LEN 505 #define INF 10000000 int GMAP[GMAP_LEN][GMAP_LEN]; int final[GMAP_LEN]; int result[GMAP_LEN]; void shortestPath_DIJ(int v,int len){ int i,j,newS,min; for(i=0;i<len;i++){ final[i]=0; result[i]=GMAP[v][i]; } final[v]=1; result[0]=0; for(i=1;i<len;i++){ min=INF; for(j=0;j<len;j++) if(!final[j] && min>result[j]) {min=result[j];newS=j;} final[newS]=1; for(j=0;j<len;j++){ if(!final[j] && min+GMAP[newS][j]<result[j]) result[j]=min+GMAP[newS][j]; } } } void initGMAP(int len){ int i,j; for(i=0;i<len;i++) for(j=0;j<len;j++) GMAP[i][j]=(i==j?0:INF); } void showResult(int len){ int i,j,maxIndex=0,x,y; double max=0; double timeA,timeB; shortestPath_DIJ(0,len); for(i=0;i<len;i++){ if(result[maxIndex]<result[i]&&result[i]!=INF)maxIndex=i; for(j=i+1;j<len;j++){ if(GMAP[i][j]!=INF && max<(result[i]+result[j]+GMAP[i][j])/2.0){ max=(result[i]+result[j]+GMAP[i][j])/2.0; x=i; y=j; } } } timeA=result[maxIndex]; timeB=(result[x]+result[y]+GMAP[x][y])/2.0; if(timeA>=timeB)printf("The last domino falls after %.1lf seconds, at key domino %d.\n",timeA,maxIndex+1); else printf("The last domino falls after %.1lf seconds, between key dominoes %d and %d.\n",timeB,x+1,y+1); } int main(){ int n,m,i,x,y,w,T=0; while(scanf("%d%d",&n,&m),n||m){ initGMAP(n); for(i=0;i<m;i++){ scanf("%d%d%d",&x,&y,&w); GMAP[x-1][y-1]=GMAP[y-1][x-1]=w; } printf("System #%d\n",++T); if(n==1)printf("The last domino falls after 0.0 seconds, at key domino %d.\n",1); else showResult(n); printf("\n"); } return 0; }