📄 miles_span.w
字号:
j=k>>1; /* |k/2| */ if (j>0 && (oo,(u=heap_elt(j))->dist>d)) { /* change is needed */ do@+{ o,heap_elt(k)=u; /* the hole moves to parent position */ o,u->heap_index=k; k=j; j=k>>1; /* |k/2| */ }@+while (j>0 && (oo,(u=heap_elt(j))->dist>d)); o,heap_elt(k)=v; o,v->heap_index=k; }}@ Finally, the procedure for removing the vertex with smallest key isonly a bit more difficult. The vertex to be removed is always|heap_elt(1)|. After we delete it, we ``sift down'' |heap_elt(hsize)|,until the basic heap inequalities hold once again.At a crucial point in this process, we have |j->dist<u->dist|. We cannotthen have|j=hsize+1|, because the previous steps have made |(hsize+1)->dist=u->dist=d|.@<Prior...@>=Vertex *del_heap(){@+Vertex *v; /* vertex to return */ register Vertex *u; /* vertex being sifted down */ register unsigned long k; /* hole in the heap */ register unsigned long j; /* child of that hole */ register long d; /* |u->dist|, the vertex of the vertex being sifted */ if (hsize==0) return NULL; o,v=heap_elt(1); o,u=heap_elt(hsize--); o,d=u->dist; k=1; j=2; while (j<=hsize) { if (oooo,heap_elt(j)->dist>heap_elt(j+1)->dist) j++; if (heap_elt(j)->dist>=d) break; o,heap_elt(k)=heap_elt(j); /* NB: we cannot have |j>hsize|, see above */ o,heap_elt(k)->heap_index=k; k=j; /* the hole moves to child position */ j=k<<1; /* |2k| */ } o,heap_elt(k)=u; o,u->heap_index=k; return v;}@ OK, here's how we plug binary heaps into Jarn{\'\i}k/Prim.@<Execute |jar_pr(g)| with binary heaps as the priority queue algorithm@>=init_queue=init_heap;enqueue=enq_heap;requeue=req_heap;del_min=del_heap;if (sp_length!=jar_pr(g)) { printf(" ...oops, I've got a bug, please fix fix fix\n"); return -4;}@*Fibonacci heaps.The running time of Jarn{\'\i}k/Prim with binary heaps, when the algorithm isapplied to a connected graph with $n$ vertices and $m$ edges, is $O(m\log n)$,because the total number of operations is $O(m+n)=O(m)$ and eachheap operation takes at most $O(\log n)$ time.Fibonacci heaps were invented by Fredman and Tarjan in 1984, in order@^Fibonacci, Leonardo, heaps@>@^Fredman, Michael Lawrence@>@^Tarjan, Robert Endre@>to do better than this. The Jarn{\'\i}k/Prim algorithm does $O(n)$enqueueing operations, $O(n)$ delete-min operations, and $O(m)$requeueing operations; so Fredman and Tarjan designed a data structurethat would support requeueing in ``constant amortized time.'' In otherwords, Fibonacci heaps allow us to do $m$ requeueing operations with atotal cost of~$O(m)$, even though some of the individual requeueingsmight take longer. The resulting asymptotic running time is then$O(m+n\log n)$. (This turns out to be optimum within a constantfactor, when the same technique is applied to Dijkstra's algorithm forshortest paths. But for minimum spanning trees the Fibonacci method isnot always optimum; for example, if $m\approx n\sqrt{\mathstrut\log n}$, thealgorithm of Cheriton and Tarjan has slightly better asymptoticbehavior, $O(m\log\log n)$.)Fibonacci heaps are more complex than binary heaps, so we can expectthat overhead costs will make them non-competitive unless $m$ and $n$ arequite large. Furthermore, it is not clear that the running time with simplebinary heaps will behave as $m\log n$ on realistic data, because$O(m\log n)$ is a worst-case estimate based on rather pessimisticassumptions. (For example, requeueing might rarely require manyiterations of the siftup loop.) But it will be instructive toimplement Fibonacci heaps as best we can, just to see how good theylook in actual practice.Let us say that the {\sl rank\/} of a node in a forest is the numberof children it has. A Fibonacci heap is an unordered forest of treesin which the key of each node is less than or equal to the key of eachchild of that node, and in which the following further condition,called property~F, also holds: The ranks $\{r_1,r_2,\ldots,r_k\}$ of thechildren of every node of rank~$k$, when put into nondecreasingorder $r_1\le r_2\le\cdots\le r_k$, satisfy $r_j\ge j-2$ for all~$j$.As a consequence of property F, we can prove by induction that everynode of rank~$k$ has at least $F_{k+2}$ descendants (including itself).Therefore, for example, we cannot have a node of rank $\ge30$ unlessthe total size of the forest is at least $F_{32}=2{,}178{,}309$. We cannothave a node of rank $\ge46$ unless the total size of the forestexceeds~$2^{32}$.@ We will represent a Fibonacci heap with a rather elaborate data structure,in order to guarantee the efficiency of all the necessary operations.Each node will have four pointers: |parent|, the node's parent (or|NULL| if the node is a root); |child|, one of the node's children(or undefined if the node has no children); |lsib| and |rsib|, thenode's left and right siblings. The children of each node, and theroots of the forest, are doubly linked by |lsib| and |rsib| incircular lists; the nodes in these lists can appear in any convenientorder, and the |child| pointer can point to any child.Besides the four pointers, there is a \\{rank} field, which tells howmany children exist, and a \\{tag} field, which is either 0 or~1.Suppose a node has children of ranks $\{r_1,r_2,\ldots,r_k\}$, where$r_1\le r_2\le\cdots\le r_k$. We know that $r_j\ge j-2$ for all~$j$;we say that the node has $l$ {\sl critical\/} children if there are$l$ cases of equality, where $r_j=j-2$. Our implementation willguarantee that any node with $l$ critical children will have atleast $l$ tagged children of the corresponding ranks. For example,suppose a node has seven children, of respective ranks $\{1,1,1,2,4,4,6\}$.Then it has three critical children, because $r_3=1$, $r_4=2$, and$r_6=4$. In our implementation, at least one of the children ofrank~1 will have $\\{tag}=1$, and so will the child of rank~2; so willone of the children of rank~4.There is an external pointer called |F_heap|, which indicates a nodewhose key is smallest. (If the heap is empty, |F_heap| is~|NULL|.)@<Prior...@>=void init_F_heap(d) long d;{@+F_heap=NULL;@+}@ @<Glob...@>=Vertex *F_heap; /* pointer to the ring of root nodes */@ We can save a bit of space and time by combining the \\{rank} and \\{tag}fields into a single |rank_tag| field, which contains $\\{rank}*2+\\{tag}$.Vertices in GraphBase graphs have six utility fields. That's just enoughfor |parent|, |child|, |lsib|, |rsib|, |rank_tag|, and the key field|dist|. But unfortunately we also need the |backlink| field, sowe are over the limit. That's not really so bad, however; wecan set up another array of $n$ records, and point to it. Theextra running time needed for indirect pointing does not have tobe charged to mems, because a production system involving Fibonacciheaps would simply redefine |Vertex| records to have seven utilityfields instead of six. In this way we can simulate the behavior of largerrecords without changing the basic GraphBase conventions.@^discussion of \\{mems}@>We will want an |Arc| record for each vertex in our next algorithm,so we might as well allocate storage for it now even though Fibonacciheaps need only two of the five fields.@d newarc u.A /* |v->newarc| points to an |Arc| record associated with |v| */@d parent newarc->tip@d child newarc->a.V@d lsib v.V@d rsib w.V@d rank_tag x.I@<Allocate additional space needed by the more complex algorithms...@>={@+register Arc *aa; register Vertex *uu; aa=gb_typed_alloc(g->n,Arc,g->aux_data); if (aa==NULL) { printf(" and there isn't enough space to try the other methods.\n\n"); goto done; } for (uu=g->vertices;uu<g->vertices+g->n;uu++,aa++) uu->newarc=aa;}@ The {\sl potential energy\/} of a Fibonacci heap, as we arerepresenting it, is defined to be the number of trees in the forestplus twice the total number of tagged children. When we operate on aheap, we will store potential energy to be used up later; then it willbe possible to do the later operations with only a small incrementalcost to the running time. (Potential energy is just a way to provethat the amortized cost is small; it does not appear explicitly in ourimplementation. It simply explains why the number of mems we computewill always be $O(m+n\log n)$.)Enqueueing is easy: We simply insert the new element as a new tree inthe forest. This costs a constant amount of time, including the cost ofone new unit of potential energy for the new tree.We can assume that |F_heap->dist| appears in a register, so we need notcharge a mem to fetch~it.@<Prior...@>=void enq_F_heap(v,d) Vertex *v; /* vertex that is entering the queue */ long d; /* its key (aka |dist|) */{ o,v->dist=d; o,v->parent=NULL; o,v->rank_tag=0; /* |v->child| need not be set */ if (F_heap==NULL) { oo,F_heap=v->lsib=v->rsib=v; }@+else {@+register Vertex *u; o,u=F_heap->lsib; o,v->lsib=u; o,v->rsib=F_heap; oo,F_heap->lsib=u->rsib=v; if (F_heap->dist>d) F_heap=v; }}@ Requeueing is of medium difficulty. If the key is being decreased ina root node, or if the decrease doesn't make the key less than the keyof its parent, no links need to change (except possibly |F_heap|itself). Otherwise we detach the node and its descendants from itspresent family and put this former subtree into the forest as a newtree. (One unit of potential energy must be stored with it.)The rank of the former parent, |p|, decreases by~1. If |p| is a root,we're done. Otherwise if |p| was not tagged, we tag it (and pay fortwo additional units of energy). Property~F still holds, because anuntagged node can always admit a decrease in rank. If |p| was tagged,however, we detach |p| and its remaining descendants, making it anothernew tree of the forest, with |p| no longer tagged. Removing the tagreleases enough stored energy to pay for the extra work of moving~|p|.Then we must decrease the rank of |p|'s parent, and so on, until finallywe get to a root or to an untagged node. The total net cost is at mostthree units of energy plus the cost of relinking the original node,so it is $O(1)$.We needn't clear the tag fields of root nodes, because we neverlook at them.@<Prior...@>=void req_F_heap(v,d) Vertex *v; /* vertex whose key is being reduced */ long d; /* its new |dist| */{@+register Vertex *p,*pp; /* parent and grandparent of |v| */ register Vertex *u,*w; /* other vertices being modified */ register long r; /* twice the rank plus the tag */ o,v->dist=d; o,p=v->parent; if (p==NULL) { if (F_heap->dist>d) F_heap=v; }@+else if (o,p->dist>d) while(1) { o,r=p->rank_tag; if (r>=4) /* |v| is not an only child */ @<Remove |v| from its family@>; @<Insert |v| into the forest@>; o,pp=p->parent; if (pp==NULL) { /* the parent of |v| is a root */ o,p->rank_tag=r-2;@+break; } if ((r&1)==0) { /* the parent of |v| is untagged */ o,p->rank_tag=r-1;@+break; /* now it's tagged */ }@+else o,p->rank_tag=r-2; /* tagged parent will become a root */ v=p;@+p=pp; }}@ @<Remove |v| from its family@>={ o,u=v->lsib; o,w=v->rsib; o,u->rsib=w; o,w->lsib=u; if (o,p->child==v) o,p->child=w;}@ @<Insert |v| into the forest@>=o,v->parent=NULL;o,u=F_heap->lsib;o,v->lsib=u;o,v->rsib=F_heap;oo,F_heap->lsib=u->rsib=v;if (F_heap->dist>d) F_heap=v; /* this can happen only with the original |v| */@ The |del_min| operation is even more interesting; this, in fact,is where most of the action lies. We know that |F_heap| points to thevertex~$v$ we will be deleting. That's nice, but we need to figure outthe new value of |F_heap|. So we have to look at all the children of~$v$and at all the root nodes in the forest. We have stored up enoughpotential energy to do that, but we can reclaim the potential only ifwe rebuild the Fibonacci heap so that the rebuilt version containsrelatively few trees.The solution is to make sure that the new heap has at most one rootof each rank. Whenever we have two tree roots of equal rank, we canmake one the child of the other, thus reducing the number oftrees by~1. (The new child does not violate Property~F, nor is itcritical, so we can mark it untagged.) The largest rank is always$O(\log n)$, if there are $n$ nodes altogether, and we can afford topay $\log n$ units of time for the work that isn't reclaimed frompotential energy.An array of pointers to roots of known rank is used to help controlthis part of the process.@<Glob...@>=Vertex *new_roots[46]; /* big enough for queues of size $2^{32}$ */@ @<Prio...@>=Vertex *del_F_heap(){@+Vertex *final_v=F_heap; /* the node to return */ register Vertex *t,*u,*v,*w; /* registers for manipulation of links */ register long h=-1; /* the highest rank present in |new_roots| */ register long r; /* rank of current tree */ if (F_heap) { if (o,F_heap->rank_tag<2) o,v=F_heap->rsib; else { o,w=F_heap->child; o,v=w->rsib; oo,w->rsib=F_heap->rsib; /* link children of deleted node into the list */ for (w=v;w!=F_heap->rsib;o,w=w->rsib) o,w->parent=NULL; } while (v!=F_heap) { o,w=v->rsib; @<Put the tree rooted at |v| into the |new_roots| forest@>;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -