dijkstra + heap 优化

spfa和bellman-ford复杂度是同阶的。虽然据说spfa复杂度是O(K*E)(k为某个常数,2~3),但是最坏情况下为O(N*E),卡spfa的题目无法通过。

所以有正边的时候最好用dikstra+heap优化,并且有priority queue代码量不会很大


1.bellman-ford算法:

bellman-ford算法的核心是松弛操作:

用一个while(true)循环;不断检测对于每条边,可以不可以进行松弛操作,如果没有一条边可以进行松弛操作,则退出循环。在没有负边的时候,松弛操作一定是有限的,故最后一定会退出循环。

如果图中不存在负环,那么最短路就不会经过同一个顶点两次

代码

sturct edge{ int from,to,cost; };
edge es[maxn];
int dis[maxn];
int V,E;

void BellmanFord(int s){
	for(int i=0;idis[from]+e.cost){
				dis[e.to]=dis[e.from]+e.cost;
				update=true;
			}
		}
		if(!update) break;
	}
}



考虑:为什么要循环V-1次?
答:因为最短路径肯定是个简单路径,不可能包含回路的,
如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径
如果回路的权值是负的,那么肯定没有解了

图有n个点,又不能有回路
所以最短路径最多n-1边

又因为每次循环,至少relax一边
所以最多n-1次就行了


所以可以把while(true)改为 for(int i=0;i

(本段来源于 http://blog.csdn.net/xu3737284/article/details/8973615)


2.Dijkstra算法:

在bellman-ford算法中,如果d[i]还不是最短距离的话,那么即使进行d[j]=d[i]+e.cost的更新,dis[j]也不会编程最短距离,而且,即使dis[i]没有变化,每次循环也要检查一下所有边,这是浪费时间的。

因此,进行以下优化:
(1)找到最短距离已经确定的顶点,从它出发更新顶点的最短距离,标记这个点

(2)以后不再关心标记的店

这实质上是一种DP,满足最优子结构


Dijkstra代码

int cost[maxn][maxn];
int dis[maxn];
int used[maxn];
int V;

void Dikstra(int s){
	fill(dis,dis+V,INF);
	fill(used,used+V,false);
	dis[s]=0;
	while(true){
		int nextv=-1;
		for(int i=0;idis[nextv]+cost[nextv][i])
					dis[i]=dis[nextv]+cost[nextv][i];
		}//松弛
	}
}
每次寻找最小点 V,松弛 E
故复杂度为O(V*E)


3.Dikstra的堆优化

由于每次Dikstrea都考虑一个最小的点,我们可以优化插入(松弛更新)和取出最小值两个操作,因此使用堆。但是回想dijkstra算法中,for(1..v)的大循环内,每次在unknown的集合中找到dist[]数组里最小的那个,从unknown集合中删除该结点。朴素实现需要O(V)的时间,而用堆可以用O(log(V))的时间找到。然而,光找到还没完,找到之后要松弛所有与他相邻的unknown的结点(known的结点已经都是最短路了)。注意到如果要用堆实现优化,堆中存储的结点的priority值应当是图中该店当前的离source的距离。然而松弛操作会更新该距离,这就意味着我们要到堆内部找到这个结点的位置,更新距离,再维护堆。而堆是不提供检索功能的,找到一个结点需要O(V),完全糟蹋了O(log V)的优化了。更何况STL的priority_queue没有办法去接触内部的数据了。

其实,有一个可行的解决方案:一旦某个结点被更新了距离,一律但当成新结点加进堆里。这样会造成一个一个问题:一个图中的结点可能在堆中可能同时会存在好几个。但是这个不影响结果:先出堆的一定是距离最小的那个结点。其他的结点出堆的时候图里面的对应结点一定已经在known的集合了,到时候直接无视掉弹出来的已经known的结点,继续弹下一个,知道弹出一个unknown的结点为止。这个做法最坏可能会让堆的高度加倍,然而仍然不影响O(log n)的复杂度(注意一定要用一个标记标注已经使用的节点,不然因为堆中包含一个顶点的多个版本,当弹出已经使用的顶点时,虽然不会改变结果(因为这个顶点的更小版本已经优化过了),但是徒增复杂度

(来自http://blog.csdn.net/biran007/article/details/4088132)

当然,也可以自己实现一个顶点在堆中的位置的映射,每次改变顶点距离以后用维护这个ID数组,实现也不复杂。

代码

int cost[maxn][maxn];
int dis[maxn];
int used[maxn];
int V;
int NumOfHeap;
typedef pair P;
P heap[3*maxn];
void Dikstra(int s){
	fill(dis,dis+V,INF);
	fill(used,used+V,false);
	dis[s]=0;
	NumOfHeap=0;
	heap[++NumOfHeap]=make_pair(s,0);
	while(NumOfHeap){
		P cur=heap[1];
		heap[1]=heap[--NumOfHeap];
		sink(1,NumOfHeap);
		if(used[cur.first]) continue;//如果被标记,弹出
		int curdis=cur.second;
		int curname=cur.first;
		used[curname]=true;
		for(int i=0;icost[curname][i]+curdis){
				dis[i]=curdis+cost[curname][i];
				heap[++NumOfHeap]=make_pair(i,dis[i]);
				swim(NumOfHeap);
			}
		}
	}
}


 
   





你可能感兴趣的:(dijkstra + heap 优化)