Dijkstra算法介绍及其优先队列优化和斐波那契堆优化
如果只是处于练习的目的,可以通过遍历所有节点(通常可以将所有节点存储于链表中,链表关键字为与父节点间的距离,主要卫星数据是本节点名称,及本节点的所有子节点的名称和二者间的距离)找到与源节点相距最短的节点(源节点的距离初始化为0,其他节点距离初始化为max),删除并返回该节点。但是这样做及其耗费时间。
使用优先队列(二叉堆实现,在后面讲解)删除并返回一个节点,优先队列指的是某个节点的值至多与其父节点一样大。因此,堆中的最小元素存放在根节点中(A[1])。因此,我们只需将根节点作为返回值,并删除根节点即可。
使用斐波那契堆删除并返回一个节点,斐波那契堆含有一个指向具有最小关键字的指针,因此只需删除并返回该指针即可。
伪代码:
Relax(u,v,w)//u为节点v的父节点
{
if v.d>u.d+w(u,v)//w(u,v)表示从节点u到节点v的距离
v.d=u.d+w(u,v)//更新子节点距离
v.Π=u//将节点u最为节点v的新的父节点,属性Π表示v的父节点,本人代码使用f_dis、f_time、或father表示
}
松弛操作对应优先队列和斐波那契堆中的关键字减值。
实现Dijkstra的伪代码:
Dij(G,w,s)//G表示所有节点的集合,s表示源节点
Q=G.V//使用一个最小优先队列保存节点(基于二叉堆的优先队列或者斐波那契堆)
while Q!=NIL //只有图中还有节点
u=ExtractMin(Q)
for each vertex v∈G.Adj[u]//对于节点u的每一个子节点
Relax(u,v,w)//w表示一个属性,即节点u与节点v间的距离
可见,算法本身很简单,但其涉及的函数是比较难以用高效的方法实现的。
ExtractMin(A)
if A.heap-size<1//已经是空堆
error "heap underflow"
min=A[1]
A[1]=A[A.heap-size]//这种直接赋值在实现中不可取,应该交换二者的值(地址)
A.heap-size=A.heap-size-1//实现了删除一个节点
MaxHeapIfy(A,1)
return min
HeapReduceKey(A,i,key)
if key>A[i]
error "new key is big than curren key"
while i>and A[parent[i]]<A[i]
exchange A[i] with A[parent[i]]
i=parent[i];
输入样例:
10 15
0 1 0 1 1
8 0 0 1 1
4 8 1 1 1
5 4 0 2 3
5 9 1 1 4
0 6 0 1 1
7 3 1 1 2
8 3 1 1 2
2 5 0 2 2
2 1 1 1 1
1 5 0 1 3
1 4 0 1 1
9 7 1 1 3
3 1 0 2 5
6 3 1 2 1
5 3
输出样例:
Time = 6: 5 => 4 => 8 => 3
Distance = 3: 5 => 1 => 3
我们将所有节点数和道路条数分别记为all_pos、all_way,将起点和终点分别记为start、end。将道路信息存储至结构中。
typedef struct min{
int pos;//本节点名称
int son[3][1000];//子节点名称及属性,son[0][i]表示子节点名称,
//对应的son[1][i]表示pos节点到son[0][i]节点的距离,son[2][i]表示对应时间
int k;//子节点数目(松弛操作需要)
int about_dis;//距离源节点可能的最小距离
int about_time;
int point;//根据题目表述建立的一个变量,表示节点个数
struct min *f_time;
struct min *f_dis;//父节点
}Min;
typedef struct heap{
Min *A[1000];//用于存储节点
int sta[1000];//用于存储每一个关键字(节点名称)在数组A中的位置
//如H->sta[key]表示关键字key在数组A中的位置,他是优化过程的核心。
int heap_size;//数组A中符合优先队列的元素个数
int length;//数组A的长度(为节约空间,应与all_pos一致)
}Heap;
/*dij*/
void dij(Heap *H,int opt)
{
/*求距离*/
if(opt==1)
{
while(!IsEmpty(H))
{
Min *u=ExtractMin(H,opt);
for(int i=0;i<u->k;i++)
relax(H,u,i,opt);
}
}
/*求时间*/
else
{
while(!IsEmpty(H))
{
Min *u=ExtractMin(H,opt);
for(int i=0;i<u->k;i++)
relax(H,u,i,opt);
}
}
}
Min *ExtractMin(Heap *H,int opt)
{
if(opt==1)
{
Min *u=H->A[1];
H->sta[H->A [1]->pos]=H->heap_size ;//更新关键字在数组A中的位置,关键操作。
H->sta [H->A [H->heap_size ]->pos]=1;
H->A [1]=H->A[H->heap_size ];
H->A [H->heap_size ]=u;
H->heap_size--;
MaxHeapIfy(H,1,opt);
return u;
}
else
{
Min *u=H->A[1];
H->sta[H->A [1]->pos]=H->heap_size ;
H->sta [H->A [H->heap_size ]->pos]=1;
H->A [1]=H->A[H->heap_size ];
H->A [H->heap_size ]=u;
H->heap_size--;
MaxHeapIfy(H,1,opt);
return u;
}
}
void relax(Heap *H,Min *u,int p,int opt)
{
Min *s=H->A[H->sta[u->son[0][p]]];
int i=H->sta[u->son[0][p]];
if(opt==1)
{
if(s->about_dis>(u->about_dis +u->son[1][p]))
{
s->f_dis =u;
s->about_dis=u->about_dis +u->son[1][p];
s->point=u->point+1;
while((i>1)&&(H->A[i/2]->about_dis >H->A[i]->about_dis))
{
H->sta[H->A[i/2]->pos]=i;
H->sta[H->A[i]->pos]=i/2;
Min *temp=H->A[i/2];
H->A[i/2]=H->A[i];
H->A[i]=temp;
i=i/2;
}
}
else if((s->about_dis==(u->about_dis +u->son[1][p]))&&(s->point>=u->point+1))
{
s->f_dis =u;
s->point=u->point+1;
s->about_dis=u->about_dis +u->son[1][p];
while((i>1)&&(H->A[i/2]->about_dis >H->A[i]->about_dis))
{
H->sta[H->A[i/2]->pos]=i;
H->sta[H->A[i]->pos]=i/2;
Min *temp=H->A[i/2];
H->A[i/2]=H->A[i];
H->A[i]=temp;
i=i/2;
}
}
}
/*求时间*/
else
{
if(s->about_time>(u->about_time+u->son[2][p]))
{
s->f_time=u;
s->about_time=u->about_time+u->son[2][p];
while((i>1)&&(H->A[i/2]->about_time>H->A[i]->about_time))
{
H->sta[H->A[i/2]->pos]=i;
H->sta[H->A[i]->pos]=i/2;
Min *temp=H->A[i/2];
H->A[i/2]=H->A[i];
H->A[i]=temp;
i=i/2;
}
}
else if((s->about_time==(u->about_time+u->son[2][p]))&&(s->about_dis>=(u->about_dis+u->son[1][p])))
{
s->f_time =u;
s->about_time=u->about_time+u->son[2][p];
while((i>1)&&(H->A[i/2]->about_time>H->A[i]->about_time))
{
H->sta[H->A[i/2]->pos]=i;
H->sta[H->A[i]->pos]=i/2;
Min *temp=H->A[i/2];
H->A[i/2]=H->A[i];
H->A[i]=temp;
i=i/2;
}
}
}
}
bool IsEmpty(Heap *H)
{
if(H->heap_size ==0)
return true;
return false;
}
/*与优先队列有关的函数*/
void BuildMinHeap(Heap *H,int opt)
{
/*求距离*/
if(opt==1)
{
H->heap_size=H->length;
for(int i=H->length/2;i>=1;i--)
MaxHeapIfy(H,i,opt);
}
/*求时间*/
else
{
H->heap_size=H->length;
for(int i=H->length/2;i>=1;i--)
MaxHeapIfy(H,i,opt);
}
}
void MaxHeapIfy(Heap *H,int i,int opt)
{
/*求距离*/
if(opt==1)
{
int l=2*i,r=2*i+1,min;
if((l<=H->heap_size)&&(H->A[l]->about_dis<H->A[i]->about_dis))
min=l;
else
min=i;
if((r<=H->heap_size )&&(H->A [r]->about_dis<H->A[min]->about_dis))
min=r;
if(min!=i)
{
H->sta[H->A[min]->pos]=i;
H->sta[H->A[i]->pos]=min;
Min *temp=H->A[i];
H->A[i]=H->A[min];
H->A[min]=temp;
MaxHeapIfy(H,min,opt);
}
}
/*求时间*/
else
{
int l=2*i,r=2*i+1,min;
if((l<=H->heap_size)&&(H->A[l]->about_time<H->A[i]->about_time))
min=l;
else
min=i;
if((r<=H->heap_size )&&(H->A [r]->about_time<H->A[min]->about_time))
min=r;
if(min!=i)
{
H->sta[H->A[min]->pos]=i;
H->sta[H->A[i]->pos]=min;
Min *temp=H->A[i];
H->A[i]=H->A[min];
H->A[min]=temp;
MaxHeapIfy(H,min,opt);
}
}
}
void InitializeHeap(Heap **H,int all_pos)
{
if(*H==NULL)
*H=(Heap *)malloc(sizeof(Heap));
for(int i=1;i<=all_pos;i++)
{
(*H)->A[i]=(Min *)malloc(sizeof(Min));
Min *u=(*H)->A[i];
u->pos=i-1;
(*H)->sta[u->pos]=i;
u->about_time =u->about_dis =max;
u->point=0;
u->f_dis=u->f_time=NULL;
u->k =0;
}
(*H)->heap_size=(*H)->length=all_pos;
}
void AddItem(Heap *H,Min item)
{
Min *u=H->A[H->sta [item.pos]];
u->son[0][u->k]=item.son[0][0];
u->son[1][u->k]=item.son[1][0];
u->son[2][u->k]=item.son[2][0];
u->k++;
}
nclude<stdio.h>
#include
#include
#define max 1000000;
typedef struct min{
int pos;
int son[3][1000];
int k;
int about_dis;
int about_time;
int point;
struct min *f_time;
struct min *f_dis;
}Min;
typedef struct heap{
Min *A[1000];
int sta[1000];
int heap_size;
int length;
}Heap;
void InitializeHeap(Heap **H,int all_pos);
void AddItem(Heap *H,Min item);
void dij(Heap *H,int opt);
void BuildMinHeap(Heap *H,int opt);
void MaxHeapIfy(Heap *H,int i,int opt);
void dij(Heap *H,int opt);
void relax(Heap *H,Min*u,int p,int opt);
Min *ExtractMin(Heap *H,int opt);
bool IsEmpty(Heap *H);
int main(void)
{
Heap *H=NULL;
Min item;
int i,j,argu;
int all_pos,all_way,start,end;
scanf("%d%d",&all_pos,&all_way);
InitializeHeap(&H,all_pos);
for(i=0;i<all_way;i++)
{
scanf("%d%d%d%d%d",&item.pos,&item.son[0][0],&argu,&item.son[1][0],&item.son[2][0]);
AddItem(H,item);
if(argu==0)
{
int temp=item.pos;
item.pos=item.son[0][0];
item.son[0][0]=temp;
AddItem(H,item);
}
}
scanf("%d%d",&start,&end);
H->A[H->sta [start]]->about_dis=H->A[H->sta [start]]->about_time=0;
H->A [H->sta[start]]->point=1;
/*求距离*/
BuildMinHeap(H,1);//1为距离,0为时间
dij(H,1);
/*将最短距离路径存入数组*/
int dis[1000],k1=0,all_dis;
Min *c=H->A[H->sta[end]];
all_dis=c->about_dis;
while(c->pos!=start)
{
dis[k1]=c->pos;
k1++;
c=c->f_dis;
if(c==NULL)
{
all_dis=max;
break;
}
}
if(c!=NULL)
{
dis[k1]=start;
k1++;
}
/*求时间*/
/*重新初始化部分变量*/
H->length=H->heap_size =all_pos;
BuildMinHeap(H,0);
dij(H,0);
int time[1000],k2=0,all_time;
c=H->A [H->sta[end]];
all_time=c->about_time;
while(c->pos!=start)
{
time[k2]=c->pos;
k2++;
c=c->f_time;
if(c==NULL)
{
all_time=max;
break;
}
}
if(c!=NULL)
{
time[k2]=start;
k2++;
}
/*将结果输出*/
bool arg=true;
if(k1==k2)
{
for(i=0;i<k1;i++)
{
if(time[i]!=dis[i])
arg=false;
}
if(arg)//距离和时间路线完全一致
{
printf("Time = %d; Distance = %d: %d",all_time,all_dis,dis[k1-1]);
for(i=k1-2;i>=0;i--)
printf(" => %d",dis[i]);
}
else
{
printf("Time = %d: %d",all_time,time[k2-1]);
for(i=k2-2;i>=0;i--)
printf(" => %d",time[i]);
printf("\nDistance = %d: %d",all_dis,dis[k1-1]);
for(i=k1-2;i>=0;i--)
printf(" => %d",dis[i]);
}
}
else
{
printf("Time = %d: %d",all_time,time[k2-1]);
for(i=k2-2;i>=0;i--)
printf(" => %d",time[i]);
printf("\nDistance = %d: %d",all_dis,dis[k1-1]);
for(i=k1-2;i>=0;i--)
printf(" => %d",dis[i]);
}
}
仅给出Dijkstra算法部分
/*dij函数部分*/
void dij(Min *head,int immi)
{
Min *u;
while(!IsEmpty(head))//运行时间Θ(n)
{
u=ExtractMin(head,immi);//至少(n^2)
for(int i=0;i<u->k ;i++)
relax(head,u,i,immi);
}
}
//这个函数非常耗时
Min *ExtractMin(Min *head,int immi)
{
Min *c=head,*u;
int min;
while(c!=NULL)
{
if(c->dele==false)
{
if(immi==0)
min=c->about_time;
else
min=c->about_way;
u=c;
break;
}
c=c->next;
}
Min *c0=head;
while(c0!=NULL)
{
if(immi==1)
{
if(c0->about_way <min&&c0->dele==false)
{
min=c0->about_way;
u=c0;
}
}
else
{
if(c0->about_time<min&&c0->dele==false)
{
min=c0->about_time;
u=c0;
}
}
c0=c0->next;
}
u->dele=true;
return u;
}
void relax(Min *head,Min *u,int pos,int immi)
{
Min *c=head;
while(c!=NULL)
{
if(c->pos==u->son[0][pos])
{
/*求最短距离*/
if(immi==1)
{
if(c->about_way>(u->about_way +u->son[1][pos]))
{
c->about_way=u->about_way+u->son[1][pos];
c->f_way =u;
c->point =u->point+1;
}
else if((c->about_way==(u->about_way +u->son[1][pos]))&&(c->point>(u->point+1)))
{
c->about_way=u->about_way+u->son[1][pos];
c->f_way =u;
c->point =u->point+1;
}
break;
}
/*求最小时间*/
else
{
if(c->about_time >(u->about_time +u->son[2][pos]))
{
c->about_time =u->about_time +u->son[2][pos];
c->f_time =u;
}
else if((c->about_time ==(u->about_time +u->son[2][pos]))&&(c->about_way >(u->about_way +u->son[1][pos])))
{
c->about_time =u->about_time +u->son[2][pos];
c->f_time =u;
}
}
}
c=c->next;
}
}
bool IsEmpty(Min *head)
{
Min *c=head;
while(c!=NULL)
{
if(c->dele==false)
return false;
c=c->next;
}
return true;
}
大致估计这种做法的运行时间达到Ω(n^3)。
/*我们可以利用插入节点操作建立一个由根链表的堆*/
FIB-HEAP-INSERT(H,x)
{
x.degree=0
x.p=NIL
x.child=NIL
x.mark=FALSE//这个属性不影响实现斐波那契堆的正确性,如果不理解为什么要添加此属性,那就不添加此属性。
if H.min==NIL//根链表是空的(等价于堆为空)
create a root list for H containng just x//创建一个只含x的堆
H.min=x
else insert x into H's root list //将x插入H的根链表,那么如何插入呢?
/*我们可以给H两个属性,一个是根链表最左端的节点的地址,另一个是最右端节点的地址,
然后只需按照个人爱好,将x与最左端或者最右段链接,然后分别更新最左、最右端节点的属性
*/
if x.key<H.min.key//更新最小节点
H.min=x
H.n=H.n+1
}
FIB-HEAP-EXTRACT-MIN(H)
z=H.min
if z≠NIL
for each child x of z
add x to the root list of
x.p=NIL
remove z from the root list of H
if z==right
H.min=NIL
else H.min=z.right//随便选一个节点作为最小节点
CONSOLIDATE(H)
H.n=H.n-1
return z
CONSOLIDATE(H)
let A[0..D(H.n)] be a new array//A是指针数组
for i=0 to D(H.n)
A[i]=NIL
for each node w in the root list of H//记为log
x=w
d=x.degree
while A[d]≠NIL//如果此条件成立,说明此前根链表中有一个根与当前跟的孩子数目相同,符合条件进入循环
y=A[d]
if x.key>y.key//这一步是确保“将旧的链接到到新的上面”,假如将新发现的节点链接到到之前的节点上,
//势必造成循环判断式log变得难以捉摸
exchange x with y
FIB-HEAP-LINK(H,y,x)
A[d]=NIL//在以遍历的根节点中,子节点数目为d的节点已不复存在
d=d+1
A[d]=x//根节点x多了一个孩子y
/*经过上面的操作得到了一个不知道最小关键字在何处的斐波那契堆*/
/*下面根据数组A重构斐波那契堆,找到最小关键字位置*/
H.min=NIL
for i=0 to D(H.n)
if A[i]≠ NIL
creat a root list for H containing just A[i]
H.min=A[i]
else insert A[i] into H's root list
if A[i].key<H.min.key
H.min=A[i]
/**/
FIB-HEAP-LINK(H,y,x)
remove y from the root list of H
make y a child of x,incrementing x.degree
y.mark=false//大家可以忽略此参数
FIB-HEAP-DECREASE-KEY(H,x,k)
if k>x.key
error "new key is greater then current key"
x.key=k;
y=x.p
if y≠NIL and x.key<y.key//如果x不是根节点,且x的关键字比他的爸爸的关键字小,那么维护斐波那契堆的性质,x显然要不能继续当y的儿子了
CUT(H,x,y)
CASCADING-CUT(H,y)//这个函数的作用可能是防止某个根节点的层数太多,它本身完全不影响堆的正确性,大家可以忽略此函数
/*到此步后,无论x之前是不是根节点,他现在一定是根节点*/
if x.key<H.min.key
H.min=x
/**/
CUT(H,x,y)
remove x from the child list of y,decrementing y.degree
add x to the root list of H
x.p=NIL
x.mark=FALSE
/**/
CASCADING-CUT(H,y)
z=y.p
if z≠NIL
if y.mark==FALSE
y.mark=TRUE
else CUT(H,y,x)
CASCADING-CUT(H,y)
针对上述例题,我们只考虑最短路径问题,不在讨论时间,同时忽略mark属性。
typedef struct min{
int pos;
int son[3][1000];
int k;//子节点个数
int about_dis;
int about_time;
int point;//限于题目要求添加的变量,记录节点个数
struct min *f_dis;
struct min *f_time;
/*构建堆的指针及参数(忽略mark属性)*/
int degree;
struct min *left;
struct min *right;
struct min *parent;
struct min *child;
}Min;
typedef struct fib{
Min *min;
int n;
Min *sta[1000];//节点名称为下标,存储对应地址
Min *b_root_l;//最左端根链表的位置,专业名称叫哨兵
Min *b_root_r;
}Fib;
/*dij函数*/
void dij(Fib *H)
{
Min *u;
while(!IsEmpty(H))
{
u=ExtractMin(H);
for(int i=0;i<u->k;i++)
relax(H,u,i);
}
}
bool IsEmpty(Fib *H)
{
if(H->min==NULL)
return true;
return false;
}
Min *ExtractMin(Fib *H)
{
//puts("Extract开始");
Min *z=H->min;//因为要删除z,所以还要考虑z是不是最左/最右的根节点,更新哨兵
if(z!=NULL)
{
// printf("将z(%d)的每一个孩子加入根链表:\n",z->pos);
//for each child x of z
/*先不去除z直接存储的那一个孩子,把他作为判断是否全部遍历z的孩子节点的标志*/
if(z->child!=NULL)
{
Min *flag=z->child,*x=z->child->right;//先判断z有没有子节点
// printf("xf=%d\n",x->parent->pos);
if(flag==x)//只有一个孩子节点
Addx(H,x);
else
{
while(true)
{
//printf("flag=%d\n",flag->pos);
//printf("x(%d)的真正右兄弟:%d\n",x->pos,x->right->right->right ->pos);
Min *real_right=x->right;
//将x加入根链表,这个操作建议写一个函数,虽然函数声明太多看起来有点不舒服
Addx(H,x);//第一次写:Addx(H,x)错误,z的孩子节点未更新
x->parent=NULL;
x=real_right;//第一次写:此时x指向根节点,x的属性被更新了
//printf("x的实际:%d\n",x->pos);
if(x==flag)
{
Addx(H,x);
x->parent=NULL;
z->child=NULL;
break;
}
}
}
}
//remove z from the root list
DeleteRootPoint(H,z); //易错
}//结束将z的子节点加入根节点
if(z==z->right)
H->min=NULL;
else
{
H->min=z->right;//随便找一个,下面的函数会找到一个正确的(在重新构成堆的时候)
ConsoliDate(H);//易错
}
H->n--;
return z;//居然把这玩意忘了
}
void relax(Fib *H,Min *u,int p)
{
Min *x=H->sta[u->son[0][p]];
// printf("实际:%d 理想:%d\n",x->pos,u->son[0][p]);
if(x->about_dis>(u->about_dis+u->son[1][p]))
{
/*松弛操作*/
// printf("前:子节点%d距离=%d 现:父节点%d 距离:%d\n",x->pos,x->about_dis ,u->pos,u->about_dis+u->son[1][p]);
x->f_dis=u;
x->about_dis=u->about_dis+u->son[1][p];
x->point=u->point+1;
/*堆操作*/
Min *y=x->parent;
if(y!=NULL&&x->about_dis<y->about_dis)//x的关键字减少之后要翻身做爸爸了
Cut(H,x,y);
if(x->about_dis<H->min->about_dis)//H->min 为空
H->min=x;
//printf("relax():%d %d",H->min->pos,H->min->about_dis);
}
else if((x->about_dis==(u->about_dis+u->son[1][p]))&&(x->point>(u->point+1)))
{
/*松弛操作*/
x->f_dis=u;
x->about_dis=u->about_dis+u->son[1][p];
x->point=u->point+1;
/*堆操作*/
Min *y=x->parent;
if(y!=NULL&&x->about_dis<y->about_dis)//x的关键字减少之后要翻身做爸爸了
Cut(H,x,y);
if(x->about_dis<H->min->about_dis)
H->min=x;
// printf("relax():%d %d",H->min->pos,H->min->about_dis);
}
}
/*dij函数*/
void dij(Fib *H)
{
Min *u;
while(!IsEmpty(H))
{
u=ExtractMin(H);
for(int i=0;i<u->k;i++)
relax(H,u,i);
}
}
bool IsEmpty(Fib *H)
{
if(H->min==NULL)
return true;
return false;
}
Min *ExtractMin(Fib *H)
{
//puts("Extract开始");
Min *z=H->min;//因为要删除z,所以还要考虑z是不是最左/最右的根节点,更新哨兵
if(z!=NULL)
{
// printf("将z(%d)的每一个孩子加入根链表:\n",z->pos);
//for each child x of z
/*先不去除z直接存储的那一个孩子,把他作为判断是否全部遍历z的孩子节点的标志*/
if(z->child!=NULL)
{
Min *flag=z->child,*x=z->child->right;//先判断z有没有子节点
// printf("xf=%d\n",x->parent->pos);
if(flag==x)//只有一个孩子节点
Addx(H,x);
else
{
while(true)
{
//printf("flag=%d\n",flag->pos);
//printf("x(%d)的真正右兄弟:%d\n",x->pos,x->right->right->right ->pos);
Min *real_right=x->right;
//将x加入根链表,这个操作建议写一个函数,虽然函数声明太多看起来有点不舒服
Addx(H,x);//第一次写:Addx(H,x)错误,z的孩子节点未更新
x->parent=NULL;
x=real_right;//第一次写:此时x指向根节点,x的属性被更新了
//printf("x的实际:%d\n",x->pos);
if(x==flag)
{
Addx(H,x);
x->parent=NULL;
z->child=NULL;
break;
}
}
}
}
//remove z from the root list
DeleteRootPoint(H,z); //易错
}//结束将z的子节点加入根节点
if(z==z->right)
H->min=NULL;
else
{
H->min=z->right;//随便找一个,下面的函数会找到一个正确的(在重新构成堆的时候)
ConsoliDate(H);//易错
}
H->n--;
return z;//居然把这玩意忘了
}
void relax(Fib *H,Min *u,int p)
{
Min *x=H->sta[u->son[0][p]];
// printf("实际:%d 理想:%d\n",x->pos,u->son[0][p]);
if(x->about_dis>(u->about_dis+u->son[1][p]))
{
/*松弛操作*/
// printf("前:子节点%d距离=%d 现:父节点%d 距离:%d\n",x->pos,x->about_dis ,u->pos,u->about_dis+u->son[1][p]);
x->f_dis=u;
x->about_dis=u->about_dis+u->son[1][p];
x->point=u->point+1;
/*堆操作*/
Min *y=x->parent;
if(y!=NULL&&x->about_dis<y->about_dis)//x的关键字减少之后要翻身做爸爸了
Cut(H,x,y);
if(x->about_dis<H->min->about_dis)//H->min 为空
H->min=x;
//printf("relax():%d %d",H->min->pos,H->min->about_dis);
}
else if((x->about_dis==(u->about_dis+u->son[1][p]))&&(x->point>(u->point+1)))
{
/*松弛操作*/
x->f_dis=u;
x->about_dis=u->about_dis+u->son[1][p];
x->point=u->point+1;
/*堆操作*/
Min *y=x->parent;
if(y!=NULL&&x->about_dis<y->about_dis)//x的关键字减少之后要翻身做爸爸了
Cut(H,x,y);
if(x->about_dis<H->min->about_dis)
H->min=x;
// printf("relax():%d %d",H->min->pos,H->min->about_dis);
}
}
/*维护堆的函数*/
void Cut(Fib *H,Min *x,Min *y)//错
{
//remove x from the child list of y,decrmenting y.degree
if(x==y->child)
{
if(x==x->right)
{
y->child=NULL;
}
else
{
y->child=x->right;
x->right->left=x->left;
x->left->right=x->right;
}
}
else
{
x->right->left=x->left;
x->left->right=x->right;
}
y->degree--;
//add x to the root list of H
H->b_root_r->left=x;
H->b_root_l->right=x;
x->left=H->b_root_r;
x->right =H->b_root_l;
H->b_root_r=x;
x->parent=NULL;//勿漏
}
void ConsoliDate(Fib *H)//易错函数
{
Min *A[H->n];//按理想状态,A的大小应该是根链表的数目
for(int i=0;i<H->n;i++)
A[i]=NULL;
//for each node w in the root list of H
//将根节点存入数组 ,在这个地方栽了
Min *root[H->n],*u=H->b_root_l;
int k=0;
while(u!=H->b_root_r)
{
root[k]=u;
//printf("存储到根节点:%d \n",u->pos);
u=u->right;
k++;
}
root[k]=u;
k++;
//正式开始遍历每一个根节点
for(int i=0;i<k;i++)
{
Min *x=root[i];
int d=x->degree;
while(A[d]!=NULL)
{
Min *y=A[d];
/*必须将旧的“位置”作为子节点链接到新的“位置”上,
假如旧的节点比新的节点小,为了维护堆得性质,需要交换二者位置,让旧的变成新的*/
if(x->about_dis>y->about_dis)
//ecchange x with y
Exchange(H,x,y);//易错
FibHeapLink(H,y,x);//易错
A[d]=NULL;//孩子个数为d节点不复存在
d=d+1;
}
A[d]=x;//x多了一个子节点y ,开始把他写到while里面了
}
/*根据A[i]重建堆并查找最小节点*/
H->min=NULL;
for(int i=0;i<H->n;i++)
{
if(A[i]!=NULL)
{
if(H->min==NULL)
{
//create a root list for H containg just A[i]
A[i]->right=A[i]->left=A[i];
A[i]->parent=NULL;
H->b_root_l=H->b_root_r=A[i];
//建好了
H->min=A[i];
}
else
{
//insert A[i] into H's root list
//和Addx()不同,插到最右边就行了,易错
H->b_root_r->left=A[i];
H->b_root_l->right=A[i];
A[i]->left=H->b_root_r;
A[i]->right=H->b_root_l;
H->b_root_r=A[i];
if(A[i]->about_dis<H->min->about_dis)
H->min=A[i];
}
}
}
if(H->min==NULL)
printf("consil");
}
void FibHeapLink(Fib *H,Min *y,Min *x)//易错
{
//remove y from the root list of H
DeleteRootPoint(H,y);
//make y a child of x,incrememting x.degree
if(x->child==NULL)
{
x->child=y;
y->right=y->left=y;
y->parent=x;
x->degree++;
}
else
{
//顺序可是很讲究的
x->child->right->left=y;
y->right=x->child->right;
x->child->right=y;
y->left=x->child;
y->parent=x;
x->degree++;
}
// printf("y(%d)->parent=%d\n",y->pos,y->parent->pos);
}
void FibHeapInsert(Fib *H,Min *x)//因为书上伪代码用的是x,我是更喜欢item的
{
x->degree=0;
x->parent=NULL;//前面已经初始化过了,但是我就是想再来一遍,就是玩
x->child=NULL;
if(H->min==NULL)
{
//create a root list for H containing just x
H->b_root_l=H->b_root_r =x;
x->right=x->left=x;
//这两行就建成了
H->min=x;
}
else
{
//insert x into H's root list
/*因个人喜好,我选择插到根链表的最右端,他和Addx()是不一样的*/
H->b_root_r->right=x;
H->b_root_l->left=x;
x->right=H->b_root_l;
x->left=H->b_root_r;
H->b_root_r=x;
//插完了
if(x->about_dis<H->min->about_dis)
H->min=x;
}
H->n=H->n+1;
}
/*dij函数*/
void dij(Fib *H)
{
Min *u;
while(!IsEmpty(H))
{
u=ExtractMin(H);
for(int i=0;i<u->k;i++)
relax(H,u,i);
}
}
bool IsEmpty(Fib *H)
{
if(H->min==NULL)
return true;
return false;
}
Min *ExtractMin(Fib *H)
{
//puts("Extract开始");
Min *z=H->min;//因为要删除z,所以还要考虑z是不是最左/最右的根节点,更新哨兵
if(z!=NULL)
{
// printf("将z(%d)的每一个孩子加入根链表:\n",z->pos);
//for each child x of z
/*先不去除z直接存储的那一个孩子,把他作为判断是否全部遍历z的孩子节点的标志*/
if(z->child!=NULL)
{
Min *flag=z->child,*x=z->child->right;//先判断z有没有子节点
// printf("xf=%d\n",x->parent->pos);
if(flag==x)//只有一个孩子节点
Addx(H,x);
else
{
while(true)
{
//printf("flag=%d\n",flag->pos);
//printf("x(%d)的真正右兄弟:%d\n",x->pos,x->right->right->right ->pos);
Min *real_right=x->right;
//将x加入根链表,这个操作建议写一个函数,虽然函数声明太多看起来有点不舒服
Addx(H,x);//第一次写:Addx(H,x)错误,z的孩子节点未更新
x->parent=NULL;
x=real_right;//第一次写:此时x指向根节点,x的属性被更新了
//printf("x的实际:%d\n",x->pos);
if(x==flag)
{
Addx(H,x);
x->parent=NULL;
z->child=NULL;
break;
}
}
}
}
//remove z from the root list
DeleteRootPoint(H,z); //易错
}//结束将z的子节点加入根节点
if(z==z->right)
H->min=NULL;
else
{
H->min=z->right;//随便找一个,下面的函数会找到一个正确的(在重新构成堆的时候)
ConsoliDate(H);//易错
}
H->n--;
return z;//居然把这玩意忘了
}
void relax(Fib *H,Min *u,int p)
{
Min *x=H->sta[u->son[0][p]];
// printf("实际:%d 理想:%d\n",x->pos,u->son[0][p]);
if(x->about_dis>(u->about_dis+u->son[1][p]))
{
/*松弛操作*/
// printf("前:子节点%d距离=%d 现:父节点%d 距离:%d\n",x->pos,x->about_dis ,u->pos,u->about_dis+u->son[1][p]);
x->f_dis=u;
x->about_dis=u->about_dis+u->son[1][p];
x->point=u->point+1;
/*堆操作*/
Min *y=x->parent;
if(y!=NULL&&x->about_dis<y->about_dis)//x的关键字减少之后要翻身做爸爸了
Cut(H,x,y);
if(x->about_dis<H->min->about_dis)//H->min 为空
H->min=x;
//printf("relax():%d %d",H->min->pos,H->min->about_dis);
}
else if((x->about_dis==(u->about_dis+u->son[1][p]))&&(x->point>(u->point+1)))
{
/*松弛操作*/
x->f_dis=u;
x->about_dis=u->about_dis+u->son[1][p];
x->point=u->point+1;
/*堆操作*/
Min *y=x->parent;
if(y!=NULL&&x->about_dis<y->about_dis)//x的关键字减少之后要翻身做爸爸了
Cut(H,x,y);
if(x->about_dis<H->min->about_dis)
H->min=x;
// printf("relax():%d %d",H->min->pos,H->min->about_dis);
}
}
/*维护堆的函数*/
void Cut(Fib *H,Min *x,Min *y)//错
{
//remove x from the child list of y,decrmenting y.degree
if(x==y->child)
{
if(x==x->right)
{
y->child=NULL;
}
else
{
y->child=x->right;
x->right->left=x->left;
x->left->right=x->right;
}
}
else
{
x->right->left=x->left;
x->left->right=x->right;
}
y->degree--;
//add x to the root list of H
H->b_root_r->left=x;
H->b_root_l->right=x;
x->left=H->b_root_r;
x->right =H->b_root_l;
H->b_root_r=x;
x->parent=NULL;//勿漏
}
void ConsoliDate(Fib *H)//易错函数
{
Min *A[H->n];//按理想状态,A的大小应该是根链表的数目
for(int i=0;i<H->n;i++)
A[i]=NULL;
//for each node w in the root list of H
//将根节点存入数组 ,在这个地方栽了
Min *root[H->n],*u=H->b_root_l;
int k=0;
while(u!=H->b_root_r)
{
root[k]=u;
//printf("存储到根节点:%d \n",u->pos);
u=u->right;
k++;
}
root[k]=u;
k++;
//正式开始遍历每一个根节点
for(int i=0;i<k;i++)
{
Min *x=root[i];
int d=x->degree;
while(A[d]!=NULL)
{
Min *y=A[d];
/*必须将旧的“位置”作为子节点链接到新的“位置”上,
假如旧的节点比新的节点小,为了维护堆得性质,需要交换二者位置,让旧的变成新的*/
if(x->about_dis>y->about_dis)
//ecchange x with y
Exchange(H,x,y);//易错
FibHeapLink(H,y,x);//易错
A[d]=NULL;//孩子个数为d节点不复存在
d=d+1;
}
A[d]=x;//x多了一个子节点y ,开始把他写到while里面了
}
/*根据A[i]重建堆并查找最小节点*/
H->min=NULL;
for(int i=0;i<H->n;i++)
{
if(A[i]!=NULL)
{
if(H->min==NULL)
{
//create a root list for H containg just A[i]
A[i]->right=A[i]->left=A[i];
A[i]->parent=NULL;
H->b_root_l=H->b_root_r=A[i];
//建好了
H->min=A[i];
}
else
{
//insert A[i] into H's root list
//和Addx()不同,插到最右边就行了,易错
H->b_root_r->left=A[i];
H->b_root_l->right=A[i];
A[i]->left=H->b_root_r;
A[i]->right=H->b_root_l;
H->b_root_r=A[i];
if(A[i]->about_dis<H->min->about_dis)
H->min=A[i];
}
}
}
if(H->min==NULL)
printf("consil");
}
void FibHeapLink(Fib *H,Min *y,Min *x)//易错
{
//remove y from the root list of H
DeleteRootPoint(H,y);
//make y a child of x,incrememting x.degree
if(x->child==NULL)
{
x->child=y;
y->right=y->left=y;
y->parent=x;
x->degree++;
}
else
{
//顺序可是很讲究的
x->child->right->left=y;
y->right=x->child->right;
x->child->right=y;
y->left=x->child;
y->parent=x;
x->degree++;
}
if(H->min==NULL)
printf("link");
// printf("y(%d)->parent=%d\n",y->pos,y->parent->pos);
}
void FibHeapInsert(Fib *H,Min *x)//因为书上伪代码用的是x,我是更喜欢item的
{
x->degree=0;
x->parent=NULL;//前面已经初始化过了,但是我就是想再来一遍,就是玩
x->child=NULL;
if(H->min==NULL)
{
//create a root list for H containing just x
H->b_root_l=H->b_root_r =x;
x->right=x->left=x;
//这两行就建成了
H->min=x;
}
else
{
//insert x into H's root list
/*因个人喜好,我选择插到根链表的最右端,他和Addx()是不一样的*/
H->b_root_r->right=x;
H->b_root_l->left=x;
x->right=H->b_root_l;
x->left=H->b_root_r;
H->b_root_r=x;
//插完了
if(x->about_dis<H->min->about_dis)
H->min=x;
}
H->n=H->n+1;
}
/*用户定义函数*/
void Exchange(Fib *H,Min *x,Min *y)//易错函数
{
// printf("前:x=%d y=%d\n",x->pos,y->pos);
if(x==H->b_root_l&&y!=H->b_root_r)
{
x->left->right=y;
x->right->left=y;
y->left->right=x;
y->right->left=x;
H->b_root_l=y;
}
else if(x==H->b_root_r&&y!=H->b_root_l)
{
x->left->right=y;
x->right->left=y;
y->left->right=x;
y->right->left=x;
H->b_root_r=y;
}
else if(y==H->b_root_l&&x!=H->b_root_r)
{
x->left->right=y;
x->right->left=y;
y->left->right=x;
y->right->left=x;
H->b_root_l=x;
}
else if(y==H->b_root_r&&x!=H->b_root_l)
{
x->left->right=y;
x->right->left=y;
y->left->right=x;
y->right->left=x;
H->b_root_r=x;
}
else if(x==H->b_root_l&&y==H->b_root_r)
{
x->left->right=y;
x->right->left=y;
y->left->right=x;
y->right->left=x;
H->b_root_l=y;
H->b_root_r=x;
}
else if(x==H->b_root_r&&y==H->b_root_l)
{
x->left->right=y;
x->right->left=y;
y->left->right=x;
y->right->left=x;
H->b_root_l=x;
H->b_root_r=y;
}
else
{
x->left->right=y;
x->right->left=y;
y->left->right=x;
y->right->left=x;
}
// printf("后:x=%d y=%d\n",x->pos,y->pos);
}
void Addx(Fib *H,Min *x)//易错函数
{
/*更新根链表*/
H->b_root_r->right=x;
H->b_root_l->left=x;
/*更新z的孩子*/
Min *c_l=x->left,*c_r=x->right;//x作为z的孩子节点,x变为根节点的同时也需要更新z的孩子节点
c_l->right=c_r;
c_r->left=c_l;
// printf("%d\n",x->parent->child);
//printf("add:x->left=%d x->right=%d x->pos=%d z->c=%d\n",x->left->pos,x->right->pos,x->pos,x->parent->child->pos);
/*更新x*/
x->right=H->b_root_l;
x->left=H->b_root_r;
/*更新哨兵*/
H->b_root_r=x;
x->parent=NULL;//这一步尽量写着吧,虽然主调函数也写了,但是。。。
}
void DeleteRootPoint(Fib *H,Min *z)//易错函数
{
if(z==H->b_root_l)
{
z->left->right=z->right;
z->right->left=z->left;
H->b_root_l=z->right;
}
else if(z==H->b_root_r)
{
z->left->right=z->right;
z->right->left=z->left;
H->b_root_r=z->left;
}
else
{
z->left->right=z->right;
z->right->left=z->left;
}
}
void Initialize(Fib **H,int all_pos)//容易出错的函数
{
if((*H)==NULL)
*H=(Fib *)malloc(sizeof(Fib));
(*H)->n=0;//易漏部分
(*H)->min=(*H)->b_root_l=(*H)->b_root_r=NULL;
for(int i=0;i<all_pos;i++)
{
(*H)->sta[i]=(Min*)malloc(sizeof(Min));
Min *u=(*H)->sta[i];
/*易漏部分*/
u->pos=i;
u->about_dis=u->about_time=max;
u->k=0;
u->point=0;//源节点需要修改为1
u->f_dis=u->f_time=u->left=u->right=u->parent=u->child=NULL;
}
/*下面将各个节点加入堆中(将(*H)->sta[i]作为输入哟)*/
for(int i=0;i<all_pos;i++)
FibHeapInsert(*H,(*H)->sta[i]);
}
void updata(Fib *H,Min item)
{
Min *u=H->sta[item.pos];
u->son[0][u->k]=item.son[0][0];
u->son[1][u->k]=item.son[1][0];
u->son[2][u->k]=item.son[2][0];
u->k++;
}
//易错:
//删除一个根节点要考虑他是否是最左或最右的根节点
//移动一个根节点至根节点要更新哨兵
//将某个孩子节点加入根节点要更新他的旧兄弟和新兄弟和哨兵
//交换两个节点要考虑哨兵
#include
#include
#include
#define max 1000000;
typedef struct min{
int pos;
int son[3][1000];
int k;//子节点个数
int about_dis;
int about_time;
int point;//限于题目要求添加的变量,记录节点个数
struct min *f_dis;
struct min *f_time;
/*构建堆的指针及参数(忽略mark属性)*/
int degree;
struct min *left;
struct min *right;
struct min *parent;
struct min *child;
}Min;
typedef struct fib{
Min *min;
int n;
Min *sta[1000];//节点名称为下标,存储对应地址
Min *b_root_l;//最左端根链表的位置,专业名称叫哨兵
Min *b_root_r;
}Fib;
/*dij函数*/
void dij(Fib *H);
void relax(Fib *H,Min *u,int p);//维护堆的函数,关键字减值
Min *ExtractMin(Fib *H);//维护堆的函数 ,去除并返回最小节点
bool IsEmpty(Fib *H);
/*维护堆的函数*/
void FibHeapInsert(Fib *H,Min *x);
void ConsoliDate(Fib *H);
void FibHeapLink(Fib *H,Min *y,Min *x);
void Cut(Fib *H,Min *x,Min *y);
/*用户定义函数*/
void Initialize(Fib **H,int all_pos);
void updata(Fib *H,Min item);
void Addx(Fib *H,Min *x);//针对的是y的子节点x
void DeleteRootPoint(Fib *H,Min *z);
void Exchange(Fib *H,Min *x,Min *y);
int main(void)
{
Fib *H=NULL;//使用指针,为其分配空间,而不是直接建立变量的目的是节省时间和空间
Min item;
int i,j,all_pos,all_way,start,end,argu;
scanf("%d%d",&all_pos,&all_way);
Initialize(&H,all_pos);//我们将节点0~all_pos在这一步加入堆的根节点中,然后通过下面的updata()函数更新节点信息
for(i=0;i<all_way;i++)
{
scanf("%d%d%d%d%d",&item.pos,&item.son[0][0],&argu,&item.son[1][0],&item.son[2][0]);
updata(H,item);
if(argu==0)
{
int temp=item.pos;
item.pos=item.son[0][0];
item.son[0][0]=temp;
updata(H,item);
}
}
scanf("%d%d",&start,&end);
H->sta[start]->about_dis =0;//不管时间了,只初始化距离相关的量
H->sta[start]->point=1;
H->min=H->sta[start];
dij(H);
Min *c=H->sta [end];
while(c->pos!=start)
{
printf("%d<=",c->pos);
c=c->f_dis;
if(c==NULL)
{
printf("无此路径!");
exit(1);
}
}
printf("%d\n",start);
}/*主函数结束*/
ConsilDate()函数中遍历根节点我是用两个循环实现的,我想这不是一个好的实现,希望大神给出更好的方法。
注意:文中大部分介绍来自《算法导论》第三版