暂不讨论人工智能的启发式算法,那么最短路径算法主要有Dijkstra、Bellman-Ford、Floyd,前两者是单源最短路径,Floyd是全源最短路径,当然单源算法也可以通过枚举实现全源算法。而近来颇为流行的SPFA算法应该算是Bellman-Ford算法的队列实现,三者主要区别如下:
Dijkstra 算法的特点 | 每次选择的边一定是最终最短路径上的边 | 不允许出现负边 | 一般可以采用堆结构优化,邻接表或邻接矩阵都可以,但一般稠密图时才有利,倾向于邻接矩阵实现,代码量最大 | o(n^2)其中n为顶点的数量,稠密图单源最短路径时有利 |
SPFA | 每次进行一次松弛操作,用队列维护需松弛的顶点 | 可以有负边,但不能有负的回路 | 由于常用于稀松图,一般采用邻接表实现,代码量略小于Dijkstra | o(kE),稀松图时单源,全源都有利 |
Floyd | 每次选择一个顶点,尝试去松弛所有结点 | 可以有负边,但不能有负的回路 | 由于常用于稠密图,一般采用邻接矩阵实现,代码量最小 | o(n^3),稠密图全源路径时有利 |
最短路径中最为人熟知的应该是Dijkstra算法,该算法利用最短路径的性质每次都选择到一条最终路径中的边,是贪心解决问题的经典范例,各大教材中也论述最多。而SPFA算法由于其实现简单,效率优异(主要是大部分图都是稀松的),适用范围广泛,在各类算法比赛中越来越受重视。Floyd一直是全源最短路径的不二选择,只有近来才在稀松图的全源最短路径上受到SPFA的挑战。下面主要给出三个算法的实现,当然实现倾向于代码简洁和逻辑上的可记忆。
(1)Dijkstra直接给出最简单实现,优化实现就是在选择最小值的时候使用各种 堆 结构。
void Dijkstra(int **G ,int vexnum, int *dist,int *path,) { /*initiate*/ bool *final = new bool[vexnum]; for(int i=0; i<vexnum; ++i) { final[i] = false; dist[i] = G[0][i]; if(dist[i]<maxval) { path[i] = 0; } else { path[i] = -1;} } /*visit*/ final[0] = true; for(int i=1; i<vexnum; ++i) { /*find min*/ int min = maxval; int vex = -1; for(int j=0; j<vexnum; ++j) if(!final[j] && dist[j]<min) { min = dist[j]; vex = j; } /*vist,加入连通分支点集*/ final[vex] = true; /*update*/ for(int k=0; k<vexnum; ++k) if(!final[k] && G[vex][k]<maxval) /*如果k顶点需要松弛,且连接在vex上*/ if(dist[vex]+G[vex][k]<dist[k]) /*如果能够利用vex顶点松弛 k 顶点 */ { dist[k] = dist[vex]+G[vex][k]; path[k] = vex; } } }
整个算法的框架就是:
初始化 辅助数组
做n-1次
寻找当前连通分支的最小代价邻接点
更新辅助数组(松弛操作)
(2)Floyd算法就给出那个经典的 k , i , j版本了
const int vexnum = 5; const int maxval = 65536;/*图中表示不可达的长度*/ void Floyd(int G[][vexnum],int dist[][vexnum],int path[][vexnum]) { for(int i=0; i<vexnum; ++i) for(int j=0; j<vexnum; ++j) { /*initiate the dist[][] and path[][]*/ dist[i][j] = G[i][j]; if(i!=j && dist[i][j]<maxval) { path[i][j] = i;} else { path[i][j] = -1;} } for(int k=0; k<vexnum; ++k) for(int i=0; i<vexnum; ++i) for(int j=0; j<vexnum; ++j) if( dist[i][j] > dist[i][k]+dist[k][j] )/*如果可以用k顶点松弛 i j之间的通路*/ { dist[i][j] = dist[i][k] + dist[k][j]; path[i][j] = path[k][j]; } }
算法框架清晰直接:
初始化操作
k , i ,j 三种循环
如果 [ i ][ j ]之间可以用[ k ]松弛,那么执行松弛
(3)SPFA直接给出一个SLF优化的实现,如果不优化可以略简洁一些。
#include <deque> using namespace std; const int maxval = 0x7F7F7F7F7F; struct Edge /*多用于稀松图,所以采用邻接表实现*/ { int dest; int cost; Edge* next; }; void SPFA(Edge* G[] ,int vexnum, int dist[],int path[]) { /* initilise */ for(int i=0; i<vexnum; ++i) { dist[i] = maxval; path[i] = -1;} dist[0] = 0; /*源点已经到达*/ deque<int> Q; /*双向队列维持顶点的队列*/ Q.push_back(0); /*源点入队*/ bool inQueue[maxedg] = {};/*标示顶点是否已经在队列中*/ inQueue[0] = true; /*源点入队标记*/ /*开始循环松弛*/ while(!Q.empty()) { int cur = Q.front(); /*取出队首顶点*/ Q.pop_front(); inQueue[cur]=false; /*队列操作始终和inQueue[]标记同步*/ for(Edge *p=G[cur]; p!=NULL; p=p->next )/*遍历队首关联的顶点*/ { int pv = p->dest; /*p关联的顶点*/ if(dist[cur]+p->cost < dist[pv])/*如果可松弛*/ { dist[pv] = dist[cur]+p->cost; path[pv] = cur; if(!inQueue[pv]) /*如果可入队*/ { if(!Q.empty()&&dist[pv]<dist[Q.front()])/*如果距离小于队首*/ { Q.push_front(pv);} else { Q.push_back(pv); } inQueue[pv] = true; /*入队标记*/ } } } } }
初始化 辅助数组
源点入队
如果队不空,取队首元素,进行松弛操作,被松弛的顶点不在队中则 入队