暂不讨论人工智能的启发式算法,那么最短路径算法主要有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; /*入队标记*/
}
}
}
}
}
使用了STL的<deque>,算法框架是:
初始化 辅助数组
源点入队
如果队不空,取队首元素,进行松弛操作,被松弛的顶点不在队中则 入队