Dijkstra算法+堆优化
Dijkstra算法步骤:
把顶点V分成两组:
S:已经求出最短路径的顶点集合
T=V-S:尚未确定最短路径的顶点集合
1、初始时:令S={V0} T={其余顶点} T中的顶点对应的距离值若存在<V0,Vi>,则为该边的权值,若不存在则为INF(正无穷)
2、从T中选取一个距离最小的顶点W,将该点加入集合S中。并用该点对T中顶点的距离进行修改:若加入w作为中间顶点(V0-->W-->Vn),该路径的距离比不加入W的路径更短,则修改此距离值。
3、重复上述步骤,知道S中包含所有顶点,即S=V为止
分析:
可知Dijkstra算法的复杂度是O(n*n)
Dijkstra算法在寻找集合T中距离最小的顶点W(即寻找dist数组中最小值)复杂度是O(n),在更新距离操作时复杂度为O(n)。
上述操作需要进行n次,将n个点的最短路径值确定因此复杂度为O(n)。可以发现外层的n次循环是不可避免的,因为需要求出源点到各个点的最短路径。可以优化的地方就在寻找dist数组最小值。
可以采用合适的数据结构优化该过程,这里采用了小根堆。小根堆查找最小值时复杂度为O(1),更新里面的值时复杂度为O(logn).最后可将Dijkstra复杂度降至O(nlogn).
这里使用C++ STL中的priority_queue实现小根堆的操作,因为priority_queue默认是大根堆,因此需要重载小于运算符,变成小根堆
代码实现
这里同样以一道模板题来展示该代码 (POJ - 2387 Til the Cows Come Home)
//链式前向星存图+迪杰斯特拉堆优化 #include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; const int MAX=1005; const int MAXN=4009; const int INF=0x3f3f3f3f; int head[MAX],cnt=0; int t,n,a,b,len; int dist[MAX]; bool vis[MAX]; struct Edge{ //链式前向星 int next,val,to; }Edge[MAXN]; inline void add(int u,int v,int w) { Edge[cnt].to=v; Edge[cnt].val=w; Edge[cnt].next=head[u]; head[u]=cnt++; } struct node { int pos,dist; //点的位置及距离 node(){} node(int p,int d) { pos=p; dist=d; } bool operator < (const node &rhs)const //重载 < { return dist>rhs.dist; } }; void Dij(int start) { priority_queue<node>que; for(int i=1;i<=n;i++) { dist[i]=INF; vis[i]=false; } dist[start]=0; que.push(node(start,0)); while(!que.empty()) { node temp=que.top(); //优先队列为首的元素及dist数组的最小值 que.pop(); int v=temp.pos; //筛选出最小值 if(vis[v])continue; //判断是否已经找到最小值 ,是的话跳过 vis[v]=true; for(int i=head[v];i!=-1;i=Edge[i].next) //用最小值的点为弧尾的边更新距离 { int to=Edge[i].to; if(dist[to]>dist[v]+Edge[i].val) { dist[to]=dist[v]+Edge[i].val; que.push(node(to,dist[to])); } } } } int main() { while(scanf("%d%d",&t,&n)!=EOF) { memset(head,-1,sizeof(head)); for(int i=0;i<t;i++) { scanf("%d%d%d",&a,&b,&len); add(a,b,len); add(b,a,len); } Dij(1); printf("%d ",dist[n]); } return 0; }
其他存图方式的Dijkstra算法代码在上一篇博客中介绍到最短路---Dijkstra,因为大致相仿,仅修改了寻找最小值的方式(优先队列优化)不一一贴出。
如有错误和不足的地方,欢迎指正,谢谢~