现在的位置: 首页 > 综合 > 正文

【算法设计与分析】最短路径的算法

2018年04月29日 ⁄ 综合 ⁄ 共 2755字 ⁄ 字号 评论关闭

暂不讨论人工智能的启发式算法,那么最短路径算法主要有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>,算法框架是:

   初始化 辅助数组

   源点入队

   如果队不空,取队首元素,进行松弛操作,被松弛的顶点不在队中则 入队

抱歉!评论已关闭.