首页 > 代码库 > [CLRS][CH 19]斐波那契堆

[CLRS][CH 19]斐波那契堆

斐波那契堆简介

斐波那契堆(Fibnacci Heap)有两种用途:第一,支持一系列操作,这些操作构成了所谓的可合并堆。第二,其一些操作可以在常数时间内完成,这使得这种数据结构非常适合于需要频繁调用这些操作的应用。

可合并堆(Mergeable Heap)支持如下五种操作:Make-Heap(), Insert(H, x), Minmun(H), Extract-Min(H), Union(H1, H2)。事实上,就是具备了快速合并操作的堆(Heap)。

斐波那契堆还支持额外两种操作:Decrease-Key(H, x, k), Delete(H, x)。

斐波那契堆结构

一个斐波那契堆是一系列具有最小堆序的有根树集合,每棵树都遵循最小堆性质。如图:

堆属性:

每个结点 x 包含一个指向其父节点的指针 x.parent 和一个指向其某一个孩子的指针 x.child,x 的所有构成了一个双向环形链表,称之为 x 的孩子链表。每个孩子节点 y 包含指针 y.left 和 y.right 指向兄弟节点。孩子链表中各兄弟出现的次序是任意的。

每个结点还有两个属性。把结点 x 的孩子链表中的孩子数目存储在 x.degree 中,布尔值属性 x.mark 指示结点 x 自上一次成为另一个结点的孩子后,是否是去过孩子。新节点是未被标记的,当结点 x 成为另一个结点的孩子时,便成为未被标记结点。

通过指针 H.min 来访问一个给定的斐波那契堆 H,指向该堆的最小根节点。如果堆为空,则 H.min 指向 NULL。因为根节点也构成了双向环形链表,所以根节点的次序是任意的。

根节点还有一个属性 H.n 表示当前节点数目。

势函数:

通过势函数来分析斐波那契堆性能。对于给定堆 H, 用 t(H) 表示 H 中根链表中树的树木,用 m(H) 来表示 H 中已标记结点的树木。定义斐波那契堆 H 的势函数 Φ(H) = t(H) + 2m(H)。

最大度数:

在一个 n 个结点的斐波那契堆中任何结点的最大度数都有上界 D(n)。如果仅仅支持可合并堆操作,那么 D(n) ≤ floor(lgn),当支持斐波那契堆操作时,也要求 D(n) = O(lgn)。

可合并堆操作

斐波那契堆上的一些可合并操作应尽可能延后执行。不同的操作可以进行性能平衡。例如,当执行 Extract-Min 操作后,不得不遍历根链表剩下的结点找出新的最小结点。这里便存在性能平衡问题。只要我们在执行 Extract-Min 操作后便利整个根链表,并把结点合并到最小堆序树中以减小根链表的规模。后面将讨论到,不论执行 Extract-Min 之前是什么样子,执行之后,根链表中每个节点要求有一个唯一的度数,使得根链表规模最大为 D(n)+1。

创建新的斐波那契堆:

创建一个新堆,Make-FibHeap 过程分配并返回一个斐波那契堆对象 H,其中 H.n = 0 和 H.min = NULL,H 中没有树,因为 t(H) = 0 且 m(H) = 0,所以其势 Φ(H) = 0。因此其摊还代价等于其实际代价 O(1)。

插入一个节点:

下面过程将结点 x 插入至 H 中,其中 x.key 已被赋值。

 FibHeap-Insert(List *H, Node *x) {     x.degree = 0     x.parent = NULL     x.child = NULL     x.mark = False     if (H.min == NULL)         create a root list for H containing just x         H.min = x    else        insert x into Hs root list        if (x.key < H.min.key)            H.min = x    H.n++}

势的增加量为 1,实际代价为 O(1),所以摊还代价为 O(1) + 1 = O(1)。

寻找最小节点:

可以通过指针 H.min 直接得到,摊还代价等于实际代价为 O(1)。

两个斐波那契堆合并:

该过程简单地将 H1 和 H2 的根链表链接,然后确定新的 H.min。

FibHeap-Union(List *H1, List *H2) {    H = Make-FibHeap()    H.min = H1.min    concatenate the root list of H2 with that of H1    if ((H1.min == NULL) || (H2.min != NULL && H2.min.key < H1.min.key))        H.min = H2.min    H.n = H1.n + H2.n    return H}

第1~3行将 H1 和 H2 的根链表链接成为 H 的新根链表。第2~5行设定 H 的最小根节点。第6行设置合并后的节点数目。

势的变化为 0,所以摊还代价等于实际代价 O(1)。

抽取最小结点:

该过程首先将最小节点 H.min 的每个孩子变成根节点,并从根链表中删除该结点。然后合并相同度数结点,优化根链表(这一步操作一直延迟到 Extract-Min 后才执行)。

FibHeap-Extract-Min(List *H) {    z = H.min    if (z != NULL)        for each child x of z            add x to the root list of H            x.parent = NULL        remove z from the root list of H        if (z == z.right)            H.min = NULL        else             H.min = z.right            Consolidate(H)        H.n--    return z}

下一步要合并 H 的根链表,通过调用 Consolidate(H) 来减少根链表树的树木。合并过程重复下列过程,直到每个根都具有不同的度数:
  1. 在根链表中找到两个相同度数的根 x 和 y,假定 x.key ≤ y.key;
  2. 把 y 链接到 x:从根链表移除 y,调用 FibHeap-Link 过程,使 y 成为 x 的孩子。该过程将 x.degree 增加1,并清除 y 上的标记。

过程 Consolidate(H) 使用一个辅助数组 A[0..D(H.n)] 来记录根节点对应度数的轨迹。如果 A[i]=y,那么当前的 y 是一个具有 y.degree=1 的根。计算 D(H.n) 的过程留到后面讲解。

Consolidate(List *H) {    create array A[0..D(H,n)]    for i = (0 to D(H,n))        A[i] = NULL    for each node w in the root list of H        x = w        d = x.degree        while (A[d] != NULL)            y = A[d]            if (x.key > y.key)                exchange x with y            FibHeap-Link(H, y, x)            A[d] = NULL            d++    H.min = NULL    for i = (0 to D(H,n))        if A[i] != NULL            if H.min == NULL            create a root list for H containing just A[i]        else            insert A[i] into Hs root list            if A[i].key < H.min.key                H.min = A[i]}FibHeap-Link(List *H, Node *y, Node *x) {    remove y from the root list of H    make y a child of x, incrementing x.degree    y.mark = False}

抽取最小节点的摊还代价为 O(lgn)。

斐波那契堆操作-降权和删除

关键字降权:

关键字降权的时候,如果被降权的结点是一个根节点的子节点,则需要判断降权后是否违反最小堆性质。如果子节点比根节点小,则直接将子结点树剪切至斐波那契堆根链表中。如果被剪切子节点的父节点没有标记,则打上标记。如果有标记,则将其父节点也剪切至根链表中,这称之为级联剪切。级联剪切一直向上回溯,直至遇到一个未被标记结点或者根节点。

FibHeap-Decrease-Key(List *H, Node *x, int key) {    if (key > x.key)        error "New key is greater than current one."    x.key = key    y = x.parent    if (y != NULL && x.key < y.key)        Cut(H, x, y)        Cacsading-Cut(H, y)    if (x.key < H.min.key)        H.min = x.key}Cut(List *H, Node *x, Node *y) {    remove x from the child list of y, y.degree--    add x to the root list of H    x.parent = NULL    x.mark = False}Cascading-Cut(List *H, Node *y) {    z = y.parent    if (z != NULL)        if (y.mark == False)            y.mark = True        else            Cut(H, y, z)            Cascading-Cut(H, z)}

关键字降权的摊还代价至多为 O(1)。之所以之前有2倍于节点数目的势,一个单位的势支付切断和标记位的清除,另一个单位补偿了被剪切结点成为根节点后多出来的势。

删除节点:

非常简单,降权至-∞后直接 Extract 出去。

FibHeap-Delete(List *H, Node *x) {    FibHeap-Decrease-Key(H, x, -∞)    FibHeap-Extract-Min(H)}

斐波那契堆理论分析

对于斐波那契堆中的每个结点 x,定义 size(x) 为以 x 为根的子树中包括 x 本身在内的结点个数。我们将证明 size(x) 是 x.degree 的幂。

引理1:设 x 是斐波那契堆中的任意结点,假定 x.degree = k。设 y1, y2, ..., yk 表示 x 的孩子,并以他们链入 x 的先后顺序排列,则 y1.degree ≥ 0,且对于 i = 2, 3, ..., k,有 yi.degree ≥ i-2。

引理2:对于所有整数 k ≥ 0,Fibk+2 = 1 + sigma(i = 0, k)Fibi

引理3:对于所有整数 k ≥ 0,斐波那契数的第 k+2 个数满足 Fibk+2 ≥ φk,其中 φ = (1 + 根号5)/2。

引理4:设 x 是斐波那契堆中的任意结点,并设 k = x.degree,则有 size(x) ≥ Fibk+2 ≥ φk

引理5:一个 n 个节点的斐波那契堆中任意结点的最大度数 D(n) = O(lgn)。

 

[CLRS][CH 19]斐波那契堆