关于学习资料的话窝看的是这篇文章
其对于数据结构的定义已经讲述得比较清楚了,我这里只说明几个小地方和给出自己的实现代码
首先,大家都知道dijkstra,用来求解单源最短路的一个算法
时间复杂度O(V^2)
然后其优化:
堆+dij
时间复杂度O(VlgV+E)
这里是重点:
大家都以为自己用二叉堆写出来的dij+堆是VlgV+E的,但是究竟是怎样的呢?
首先对于删点n-1次,单次复杂度lgV
然后对于每次松弛修改结点的操作最坏有E次(对于每一条边都需要进行松弛操作时),单次复杂度lgV。
这就很尴尬了,这样计算出来的复杂度为(E+V)lgV,而并不是人们所期待的E+VlgV,但是因为普通二叉堆比较好写,而在竞赛中想通过lgV卡掉的情况并不太多,所以说为了编程效率,大家普遍使用它去优化dij,甚至有用priority_queue的,这当然没问题(:常数不小如果也叫没问题的话。。。
当然,这里证明一下普通的dij+二叉堆的时间复杂度不是人们所期望的复杂度只是强迫症表示一定要说而已。
那么如何达到O(E+VlgV)这个复杂度?
首先,对于每次选出最小值以及删除操作必须要在lgV时间内做完
其次,对于每次松弛操作,必须是O(1)的;
那么。。。对于使用什么数据结构来维护这些操作,我查了比较多的资料,待选的有两种,一个是斐波那契堆,另外一个就是过会要讲的pairing heap
相关的博客我推荐两个:
一个是这个:
http://dsqiu.iteye.com/blog/1714961
看了这个才知道有pairing heap 这个东西,写的还挺清楚的,不过中间描述斐波那契堆的部分图裂了,我看的有点费劲(不知道是不是窝网络问题),这里面也有源码,不过到了pairing heap以后我就没看他的了。
另外一个是这个:
http://blog.csdn.net/goodluckwhh/article/details/13341893
这个博客就只是讲fibonacci heap 的。。。这个里面的图挺清晰的,思想描述的也不错,不过没代码
然后F-heap它复杂度确实没问题,但是我搞了一个下午以后还是放弃了用它优化dij。
理由:
1.太难写了,数据结构部分就要250+行(也许是我太low了,窝反正不愿意比赛的时候敲二百行这么个玩意
2.常数太大,能不大么。。。维护了一堆玩意。
然后我就把它扔了,搞了一下pairing heap 这个玩意,这个挺好写的,大概数据结构部分70行就能写完,压压行的话也许可以更少,不过我没试。
这个东西我是看文章最开始部分的那个ppt学的:
我只把里面没解释清楚的的部分解释一下,我下面说的请务必看完ppt再来看(如果不懂的话
1.首先,左兄弟(当是其父节点的第一个孩子时,这个变量指向其父亲,右兄弟(并不是指向同一层的,不要以为同一层的就是兄弟OK?
2.关于删除根节点均摊复杂度lgV的部分,这个ppt里面没证,我说一下:
首先,删除根结点的时候,需要合并根节点的所有子树,这一步最坏情况,明显是O(N),没有什么问题
但是合并方式会对你以后删除节点产生影响:
第一种,最普通的,按顺序合并,那么合并完了以后还是有可能根节点上连了n-1个点,那么后面的每次仍然是O(N);
第二种,就是分治的思路合并,两个两个合并,然后,再两个两个合并,这样,第一次最坏删除根节点的时候,复杂度仍为O(N),但是,树的形态发生了改变,它变的更“圆润”,因为每次合并时,最后作为根节点的那个节点的度数会+1,而这样的操作会进行lgN次因此,后面再进行删除操作的时候就是O(lgN)了
3.其对优化dij的复杂都的证明:
首先,第一次选点+删除最坏O(V);
然后,假设每次循环进行Ei次松弛操作,并且这Ei个点都没有子节点直接连上了根节点,并仍以删除后的根节点为根结点,那么此时每次删除的操作是Ei+lgV
循环V-1次,就有O(V)+O(E1+E2+E3+…+Ei+…+Ev-1)+O(VlgV)=O(E+VlgV)
证毕
接下来放代码,我用的是静态开点的办法例题用的是水题hdu2544
//终于找到了一个比较好写的可以用来优化dij的数据结构
//pairing heap
#include <stdio.h>
#include<cstdlib>
#include<queue>
#include<iostream>
#include<cstring>
using namespace std;
const int mxn=100+10;
const int mx=30*mxn;
const int ln=1000000000;
struct pairingheap{
int siz;
int l[mxn];//左邻,若为其父节点的首个孩子节点,则其值为其父亲节点
int r[mxn];//右邻
int k[mxn];//节点的值
int cld[mxn];//孩子列表里的第一个节点;
int root;//根节点标号
int flag[mxn];//dij用
int qu[mx];
int hd,ed;
void init(){
siz=0;
memset(l,-1,sizeof(l));
memset(r,-1,sizeof(r));
memset(cld,-1,sizeof(cld));
root=-1;
hd=1;
ed=0;
}
int merg(int t1,int t2){//合并两棵树
if(k[t1]<k[t2]){//让k[t2]<=k[t1]
t1^=t2;
t2^=t1;
t1^=t2;
}
l[t1]=t2;
r[t1]=cld[t2];
if(cld[t2]!=-1)l[cld[t2]]=t1;
cld[t2]=t1;
l[t2]=r[t2]=-1;
return t2;//返回根节点
}
void add(int f,int key){//添加一个标号为f,值为key的节点
k[++siz]=key;
flag[f]=siz;
if(root==-1){
root=siz;
return ;
}
root=merg(siz,root);
}
void decrease(int vis,int val){//减小vis节点的值为val(这里我写的是小顶的,值变小可以这么干,变大的话会稍微麻烦一点,但是dij不会出现增大的情况,所以不要担心)
if(vis==root){k[vis]=val;return;}
if(cld[l[vis]]!=vis)r[l[vis]]=r[vis];
else cld[l[vis]]=r[vis];
if(r[vis]!=-1)l[r[vis]]=l[vis];
k[vis]=val;
l[vis]=r[vis]=-1;
root=merg(vis,root);
}
void print(int n){
for(int i=1;i<=n;i++){
printf("%d %d %d %d %d %d\n",i,l[i],r[i],k[i],cld[i],root);
}
}
void remov(){
int fst=cld[root];
while(fst!=-1){
qu[++ed]=fst;
fst=r[fst];
}
while(hd<ed){
int q1=qu[hd++];
int q2=qu[hd++];
qu[++ed]=merg(q1,q2);
}
fst=qu[hd++];
hd=1,ed=0;
root=fst;
}
}p;
int mp[mxn][mxn];
int d[mxn];
int vis[mxn];
vector<int>mmp[mxn];
void dij(int st,int n){
int siz=mmp[st].size();
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++){
d[i]=ln;
}
for(int i=0;i<siz;i++){
int des=mmp[st][i];
d[des]=mp[st][des];
}
d[st]=0;
p.init();
for(int i=1;i<=n;i++){
p.add(i,d[i]);
}
for(int i=1;i<n;i++){
int tt=p.root;//p.print(n);puts("");
p.remov();
vis[tt]=1;
siz=mmp[tt].size();
for(int j=0;j<siz;j++){
int des=mmp[tt][j];
if(!vis[des]){
int dis=d[tt]+mp[tt][des];
if(dis<d[des]){
d[des]=dis;
p.decrease(des,dis);
}
}
}
}
}
int main(void)
{
int n,m;
while(scanf("%d%d",&n,&m)&&(n||m)){
int a,b,c;
memset(mp,-1,sizeof(mp));
for(int i=1;i<=n;i++){
mmp[i].clear();
mp[i][i]=0;
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
if(mp[a][b]!=-1)mp[a][b]=mp[b][a]=min(mp[a][b],c);
else {
mp[a][b]=mp[b][a]=c;
mmp[a].push_back(b);
mmp[b].push_back(a);
}
}
dij(1,n);
printf("%d\n",d[n]);
}
return 0;
}
额。。。我本来是打算用flag存一下标号和堆中标号的映射关系,后来发现因为我开始是添进去了N个结点,所以说,直接存取就好了,想去掉可以直接去掉。
以上