结构之美——优先队列三大结构(二)——斐波那契堆(Fibonacci Heap)

1.简介

斐波那契堆是一种松散的二项堆,与二项堆的主要区别在于构成斐波那契堆得树可以不是二项树,并且这些树的根排列是无序的(二项堆的根结点排序从左到右是按照结点个数排序的,不是按照根结点的大小)。

斐波那契堆得优势在于它对建堆、插入、抽取最小关键字、联合等操作能在O(1)的时间内完成(不涉及删除元素的操作仅需要O(1))。这是对二项堆效率的巨大改善。在EXACT-MIN|, DELETE的操作数目较小时,斐波那契堆是很理想的,例如:某些图问题算法对每条边都要调用一次DECREASE-KEY(图的最小割:Graph-cut:Min-Cut Problem )。对许多稠密图来说,每一次DECREASE-KEY调用的O(1)加起来,就是对二叉或者二项堆的最坏情况的一种显著改善。用于解决最小生成树和寻找单源最短路径等问题的快速算法都要用到斐波那契堆。

但是,从实际上看,对于大多数应用来说,由于斐波那契堆得常数因子以及程序设计上的复杂度,使它不如通常的二叉堆合适。因此,它的价值大多存在于理论意义上(如果设计出一种与斐波那契堆具有相同平摊时间界但又简单得多数据结构,那他就会有很大的实用价值)。

斐波那契堆如果不进行DECREASE-KEY,DELETE,则堆中的树与二项树一致。由于相对于二项堆,斐波那契堆更松散,合并操作只发生在抽取一个结点之后,也就是说斐波那契堆的维护可以(总是)被延后到方便时再做。

 

1.1斐波那契堆结点ADT

结点含有以下域:

1) 父节点p[x]

2) 指向任一子女的指针child[x]——结点x的子女被链接成一个环形双链表,称为x的子女表

3) 左兄弟left[x]

4) 右兄弟right[x]——当left[x] = right[x] = x时,说明x是独子。

5) 子女的个数degree[x]

6) 布尔值域mark[x]——标记是否失去了一个孩子

//斐波那契结点ADT
struct FibonacciHeapNode {
    int key;       //结点
    int degree;    //度
    FibonacciHeapNode * left;  //左兄弟
    FibonacciHeapNode * right; //右兄弟
    FibonacciHeapNode * parent; //父结点
    FibonacciHeapNode * child;  //第一个孩子结点
    bool marked;           //是否被删除第1个孩子
};
typedef FibonacciHeapNode FibNode;
 


1.2斐波那契堆ADT

对于一个给定的斐波那契堆H,可以通过指向包含最小关键字的树根的指针min[H]来访问,这个结点被称为斐波那契堆中的最小结点。如果一个斐波那契堆H是空的,则min[H] = NIL. 在一个斐波那契堆中,所有树的根都通过left和right指针链接成一个环形的双链表,称为堆的根表。于是,指针min[H]就指向根表中具有最小关键字的结点(就是查找最小结点的操作,下文就没有再介绍了)。

//斐波那契堆ADT
struct FibonacciHeap {
    int keyNum;   //堆中结点个数
    FibonacciHeapNode * min;//最小堆,根结点
    int maxNumOfDegree;   //最大度
    FibonacciHeapNode * * cons;//指向最大度的内存区域
};
 
typedef FibonacciHeap FibHeap;

 

 

 

2.斐波那契堆操作

 

2.1.创建斐波那契堆

创建一个空的斐波那契堆,过程MAKE-FIB-HEAP 分配并返回一个斐波那契堆对象H; 

//初始化一个空的Fibonacci Heap
FibHeap * FibHeapMake() {
    FibHeap * heap = NULL;
    heap = (FibHeap *) malloc(sizeof(FibHeap));
    if (NULL == heap) {
        puts("Out of Space!!");
        exit(1);
    }
    memset(heap, 0, sizeof(FibHeap));
    return heap;
}
 
//初始化结点x
FibNode * FibHeapNodeMake() {
    FibNode * x = NULL;
    x = (FibNode *) malloc(sizeof(FibNode));
    if (NULL == x) {
        puts("Out of Space!!");
        exit(1);
    }
    memset(x, 0, sizeof(FibNode));
    x->left = x->right = x;
    return x;
}


 

 

2.2.插入一个结点

要插入一个结点x,对结点的各域初始化,赋值,然后构造自身的环形双向链表后,将x加入H的根表中。 也就是说,结点x 成为一棵单结点的最小堆有序树,同时就是斐波那契堆中一棵无序树而且在根表最小结点的左边。

如图是将关键字为21的结点插入斐波那契堆。该结点自成一棵最小堆有序树,从而被加入到根表中,成为根的左兄弟。 

 

 

 
FIB-HEAP-INSERT(H, x)

1  degree[x]  0

2  p[x]  NIL

3  child[x]  NIL

4  left[x] x

5  right[x] x

6  mark[x]  FALSE

7  concatenate the root list containing x with root list H 

8  if min[H] = NIL or key[x] < key[min[H]]

9     then min[H] x

10  n[H] n[H] + 1

 

 
 

 

2.3.寻找最小结点

由于存在min[H]这个指针,故可以在O(1)内找到最小结点。

2.4.合并两个堆

不同于二项堆,这个操作在斐波那契堆里非常简单。仅仅简单地将H1和H2的两根表串联,然后确定一个新的最小结点。

 

FIB-HEAP-UNION(H1,H2)

1   H  MAKE-FIB-HEAP()

2   min[H] min[H1]

3   concatenate the root list of H2 with the root list of H

4   if (min[H1] = NIL) or (min[H2]  NIL and min[H2] < min[H1])

5   then min[H] min[H2]

6   n[H] n[H1] + n[H2]

7   free the objects H1 and H2

8   return H

2.5.抽取(删除)最小结点(MOST)

抽取最小结点的操作是斐波那契堆最复杂的操作,就是过程比较多,下面一步一步介绍。

1)将要抽取最小结点的子树都直接串联在根表中,如图(b)

2)然后就是合并所有degree相等的树,直到没有相等的degree的树。

(1)先定义一个数组A,数组A保存的根表子树中的头结点,A[i]=y表示树 y 的degree值是 i 。这样就可以得到数组A的长度就等于当前斐波那契堆中degree的最大值。

(2)当发现两个相等的degree的树,就执行合并操作。“发现”是这样得到的——向右遍历根表的结点,得到当前子树的degree的值,如果该degree值在A数组已经有元素则就是degree相等,就可以合并了。如图(e)(f)就是将degree等于 1的两个子树合并。

 

FIB-HEAP-EXTRACT-MIN(H)

1  z min[H]

2  if z  NIL

3      then for each child x of z

4               do add x to the root list of H

5                  p[x]  NIL

6           remove z from the root list of H

7           if z = right[z]

8              then min[H]  NIL

9              else min[H] right[z]

10                   CONSOLIDATE(H)

11           n[H] n[H] - 1

12  return z

合并操作:

CONSOLIDATE(H)

1 for i  0 to D(n[H])

2      do A[i]  NIL

3 for each node w in the root list of H //from min(H)

4      do x w

5         d degree[x]

6         while A[d]  NIL  //辅助数组中该degree存在

7            do y A[d]

8               if key[x] > key[y]

9                  then exchange x y

10                FIB-HEAP-LINK(H,y,x)

11                A[d]  NIL

12                d d + 1

13         A[d] x

14 min[H]  NIL

15 for i  0 to D(n[H])

16      do if A[i]  NIL

17            then add A[i] to the root list of H

18                 if min[H] = NIL or key[A[i]] < key[min[H]]

19                    then min[H] A[i]

 
  

更新指针域:

FIB-HEAP-LINK(H, y, x)

1  remove y from the root list of H

2  make y a child of x, incrementing degree[x]

3  mark[y]  FALSE

 

2.6.减小一个关键字(剪枝&级联剪枝)

减小一个关键字的字,会破坏最小堆的性质,所以要进行最小堆维护。因为斐波那契支持减小关键字和删除结点操作,所以斐波那契堆的子树就不一定是二项树了。

减小一个关键字主要进行两个步骤:

1)减小关键字,如果破坏最小堆性质,则将该结点a直接从原来的树移除直接串联在根表中,并将父结点p的mark属性设置成为true(mark域来获得期望时间界)。

2)进行级联剪枝:如果当前父结点p的mark属性是true,且p的父结点pp的mark属性也是true,那么将p从pp移除加入到根表中

一旦失去第二个孩子,当前节点与其父亲结点之间的联系被切断,并成为一个新根。

下图中,黑色的结点表示其mark属性为true,(a)(b)将关键字46减少为15,没有发生级联剪枝;(c,d,e)将35减小为5,发生了级联剪枝操作:

FIB-HEAP-DECREASE-KEY(H,x,k)

1  if k > key[x]

2     then error "new key is greater than current key"

3  key[x] k

4  y p[x]

5  if y  NIL and key[x] < key[y]

6     then CUT(H,x,y)

7           CASCADING-CUT(H,y)

8  if key[x] < key[min[H]]

9     then min[H] x

 
  

剪枝:

CUT(H,x,y)

1  remove x from the child list of y, decrementing degree[y]

2  add x to the root list of H

3  p[x]  NIL

4  mark[x]  FALSE

 
  

级联剪枝:

CASCADING-CUT(H,y)

1  z p[y]

2  if z  NIL

3     then if mark[y] = FALSE

4             then mark[y]  TRUE

5             else CUT(H,y,z)

6                  CASCADING-CUT(H,z)

 

 

2.7.删除一个结点

删除结点X,首先将X的关键字值减小到比最小结点的值更小(调用减小一个关键字的过程),然后删除(抽取)最小结点就可以了。 

FIB-HEAP-DELETE(H, x)

1  FIB-HEAP-DECREASE-KEY(H, x, -)

2  FIB-HEAP-EXTRACT-MIN(H)

 

 

3.斐波那契堆完整实现

 

//说明:
//代码中Fibonacci Heap 用变量heap表示
//结点通常用x,y等表示
#include
#include
#include
#include
#include
using namespace std;
 
//斐波那契结点ADT
struct FibonacciHeapNode {
    int key;       //结点
    int degree;    //度
    FibonacciHeapNode * left;  //左兄弟
    FibonacciHeapNode * right; //右兄弟
    FibonacciHeapNode * parent; //父结点
    FibonacciHeapNode * child;  //第一个孩子结点
    bool marked;           //是否被删除第1个孩子
};
 
typedef FibonacciHeapNode FibNode;
 
//斐波那契堆ADT
struct FibonacciHeap {
    int keyNum;   //堆中结点个数
    FibonacciHeapNode * min;//最小堆,根结点
    int maxNumOfDegree;   //最大度
    FibonacciHeapNode * * cons;//指向最大度的内存区域
};
 
typedef FibonacciHeap FibHeap;
 
/*****************函数申明*************************/
//将x从双链表移除
inline void FibNodeRemove(FibNode * x);
 
//将x堆结点加入y结点之前(循环链表中)
void FibNodeAdd(FibNode * x, FibNode * y);
 
//初始化一个空的Fibonacci Heap
FibHeap * FibHeapMake() ;
 
//初始化结点x
FibNode * FibHeapNodeMake();
 
//堆结点x插入fibonacci heap中
void FibHeapInsert(FibHeap * heap, FibNode * x);
 
//将数组内的值插入Fibonacci Heap
void FibHeapInsertKeys(FibHeap * heap, int keys[], int keyNum);
 
//将值插入Fibonacci Heap
static void FibHeapInsertKey(FibHeap * heap, int key);
 
//抽取最小结点
FibNode * FibHeapExtractMin(FibHeap * heap);
 
//合并左右相同度数的二项树
void FibHeapConsolidate(FibHeap * heap);
 
//将x根结点链接到y根结点
void FibHeapLink(FibHeap * heap, FibNode * x, FibNode *y);
 
//开辟FibHeapConsolidate函数哈希所用空间
static void FibHeapConsMake(FibHeap * heap);
 
//将堆的最小结点移出,并指向其有兄弟
static FibNode *FibHeapMinRemove(FibHeap * heap);
 
//减小一个关键字
void FibHeapDecrease(FibHeap * heap, FibNode * x, int key);
 
//切断x与父节点y之间的链接,使x成为一个根
static void FibHeapCut(FibHeap * heap, FibNode * x, FibNode * y);
 
//级联剪切
static void FibHeapCascadingCut(FibHeap * heap, FibNode * y);
 
//修改度数
void renewDegree(FibNode * parent, int degree);
 
//删除结点
void FibHeapDelete(FibHeap * heap, FibNode * x);
 
//堆内搜索关键字
FibNode * FibHeapSearch(FibHeap * heap, int key);
 
//被FibHeapSearch调用
static FibNode * FibNodeSearch(FibNode * x, int key);
 
//销毁堆
void FibHeapDestory(FibHeap * heap);
 
//被FibHeapDestory调用
static void FibNodeDestory(FibNode * x);
 
//输出打印堆
static void FibHeapPrint(FibHeap * heap);
 
//被FibHeapPrint调用
static void FibNodePrint(FibNode * x);
/************************************************/
 
//将x从双链表移除
inline void FibNodeRemove(FibNode * x) {
    x->left->right = x->right;
    x->right->left = x->left;
}
 
/*
将x堆结点加入y结点之前(循环链表中)
    a …… y
    a …… x …… y
*/
inline void FibNodeAdd(FibNode * x, FibNode * y) {
    x->left = y->left;
    y->left->right = x;
    x->right = y;
    y->left = x;
}
 
//初始化一个空的Fibonacci Heap
FibHeap * FibHeapMake() {
    FibHeap * heap = NULL;
    heap = (FibHeap *) malloc(sizeof(FibHeap));
    if (NULL == heap) {
        puts("Out of Space!!");
        exit(1);
    }
    memset(heap, 0, sizeof(FibHeap));
    return heap;
}
 
//初始化结点x
FibNode * FibHeapNodeMake() {
    FibNode * x = NULL;
    x = (FibNode *) malloc(sizeof(FibNode));
    if (NULL == x) {
        puts("Out of Space!!");
        exit(1);
    }
    memset(x, 0, sizeof(FibNode));
    x->left = x->right = x;
    return x;
}
 
//堆结点x插入fibonacci heap中
void FibHeapInsert(FibHeap * heap, FibNode * x) {
    if (0 == heap->keyNum) {
        heap->min = x;
    } else {
        FibNodeAdd(x, heap->min);
        x->parent = NULL;
        if (x->key < heap->min->key) {
            heap->min = x;
        }
    }
    heap->keyNum++;
}
 
//将数组内的值插入Fibonacci Heap
void FibHeapInsertKeys(FibHeap * heap, int keys[], int keyNum) {
    for (int i = 0; i < keyNum; i++) {
        FibHeapInsertKey(heap, keys[i]);
    }
}
 
//将值插入Fibonacci Heap
static void FibHeapInsertKey(FibHeap * heap, int key) {
    FibNode * x = NULL;
    x = FibHeapNodeMake();
    x->key = key;
    FibHeapInsert(heap, x);
}
 
//抽取最小结点
FibNode * FibHeapExtractMin(FibHeap * heap) {
    FibNode * x = NULL, * z = heap->min;
    if (z != NULL) {
 
        //删除z的每一个孩子
        while (NULL != z->child) {
            x = z->child;
            FibNodeRemove(x);
            if (x->right == x) {
                z->child = NULL;
            } else {
                z->child = x->right;
            }
            FibNodeAdd(x, z);//add x to the root list heap
            x->parent = NULL;
        }
 
        FibNodeRemove(z);
        if (z->right == z) {
            heap->min = NULL;
        } else {
            heap->min = z->right;
            FibHeapConsolidate(heap);
        }
        heap->keyNum--;
    }
    return z;
}
 
//合并左右相同度数的二项树
void FibHeapConsolidate(FibHeap * heap) {
    int D, d;
    FibNode * w = heap->min, * x = NULL, * y = NULL;
    FibHeapConsMake(heap);//开辟哈希所用空间
    D = heap->maxNumOfDegree + 1;
    for (int i = 0; i < D; i++) {
        *(heap->cons + i) = NULL;
    }
 
    //合并相同度的根节点,使每个度数的二项树唯一
    while (NULL != heap->min) {
        x = FibHeapMinRemove(heap);
        d = x->degree;
        while (NULL != *(heap->cons + d)) {
            y = *(heap->cons + d);
            if (x->key > y->key) {//根结点key最小
                swap(x, y);
            }
            FibHeapLink(heap, y, x);
            *(heap->cons + d) = NULL;
            d++;
        }
        *(heap->cons + d) = x;
    }
    heap->min = NULL;//原有根表清除
 
    //将heap->cons中结点都重新加到根表中,且找出最小根
    for (int i = 0; i < D; i++) {
        if (*(heap->cons + i) != NULL) {
            if (NULL == heap->min) {
                heap->min = *(heap->cons + i);
            } else {
                FibNodeAdd(*(heap->cons + i), heap->min);
                if ((*(heap->cons + i))->key < heap->min->key) {
                    heap->min = *(heap->cons + i);
                }//if(<)
            }//if-else(==)
        }//if(!=)
    }//for(i)
}
 
//将x根结点链接到y根结点
void FibHeapLink(FibHeap * heap, FibNode * x, FibNode *y) {
    FibNodeRemove(x);
    if (NULL == y->child) {
        y->child = x;
    } else {
        FibNodeAdd(x, y->child);
    }
    x->parent = y;
    y->degree++;
    x->marked = false;
}
 
//开辟FibHeapConsolidate函数哈希所用空间
static void FibHeapConsMake(FibHeap * heap) {
    int old = heap->maxNumOfDegree;
    heap->maxNumOfDegree = int(log(heap->keyNum * 1.0) / log(2.0)) + 1;
    if (old < heap->maxNumOfDegree) {
        //因为度为heap->maxNumOfDegree可能被合并,所以要maxNumOfDegree + 1
        heap->cons = (FibNode **) realloc(heap->cons,
            sizeof(FibHeap *) * (heap->maxNumOfDegree + 1));
        if (NULL == heap->cons) {
            puts("Out of Space!");
            exit(1);
        }
    }
}
 
//将堆的最小结点移出,并指向其有兄弟
static FibNode *FibHeapMinRemove(FibHeap * heap) {
    FibNode *min = heap->min;
    if (heap->min == min->right) {
        heap->min = NULL;
    } else {
        FibNodeRemove(min);
        heap->min = min->right;
    }
    min->left = min->right = min;
    return min;
}
 
//减小一个关键字
void FibHeapDecrease(FibHeap * heap, FibNode * x, int key) {
    FibNode * y = x->parent;
    if (x->key < key) {
        puts("new key is greater than current key!");
        exit(1);
    }
    x->key = key;
 
    if (NULL != y && x->key < y->key) {
        //破坏了最小堆性质,需要进行级联剪切操作
        FibHeapCut(heap, x, y);
        FibHeapCascadingCut(heap, y);
    }
    if (x->key < heap->min->key) {
        heap->min = x;
    }
}
 
//切断x与父节点y之间的链接,使x成为一个根
static void FibHeapCut(FibHeap * heap, FibNode * x, FibNode * y) {
    FibNodeRemove(x);
    renewDegree(y, x->degree);
    if (x == x->right) {
        y->child = NULL;
    } else {
        y->child = x->right;
    }
    x->parent = NULL;
    x->left = x->right = x;
    x->marked = false;
    FibNodeAdd(x, heap->min);
}
 
//级联剪切
static void FibHeapCascadingCut(FibHeap * heap, FibNode * y) {
    FibNode * z = y->parent;
    if (NULL != z) {
        if (y->marked == false) {
            y->marked = true;
        } else {
            FibHeapCut(heap, y, z);
            FibHeapCascadingCut(heap, z);
        }
    }
}
 
//修改度数
void renewDegree(FibNode * parent, int degree) {
    parent->degree -= degree;
    if (parent-> parent != NULL) {
        renewDegree(parent->parent, degree);
    }
}
 
//删除结点
void FibHeapDelete(FibHeap * heap, FibNode * x) {
    FibHeapDecrease(heap, x, INT_MIN);
    FibHeapExtractMin(heap);
}
 
//堆内搜索关键字
FibNode * FibHeapSearch(FibHeap * heap, int key) {
    return FibNodeSearch(heap->min, key);
}
 
//被FibHeapSearch调用
static FibNode * FibNodeSearch(FibNode * x, int key) {
    FibNode * w = x, * y = NULL;
    if (x != NULL) {
        do {
            if (w->key == key) {
                y = w;
                break;
            } else if (NULL != (y = FibNodeSearch(w->child, key))) {
                break;
            }
            w = w->right;
        } while (w != x);
    }
    return y;
}
 
//销毁堆
void FibHeapDestory(FibHeap * heap) {
    FibNodeDestory(heap->min);
    free(heap);
    heap = NULL;
}
 
//被FibHeapDestory调用
static void FibNodeDestory(FibNode * x) {
    FibNode * p = x, *q = NULL;
    while (p != NULL) {
        FibNodeDestory(p->child);
        q = p;
        if (p -> left == x) {
            p = NULL;
        } else {
            p = p->left;
        }
        free(q->right);
    }
}
 
//输出打印堆
static void FibHeapPrint(FibHeap * heap) {
    printf("The keyNum = %d\n", heap->keyNum);
    FibNodePrint(heap->min);
    puts("\n");
};
 
//被FibHeapPrint调用
static void FibNodePrint(FibNode * x) {
    FibNode * p = NULL;
    if (NULL == x) {
        return ;
    }
    p = x;
    do {
        printf(" (");
        printf("%d", p->key);
        if (p->child != NULL) {
            FibNodePrint(p->child);
        }
        printf(") ");
        p = p->left;
    }while (x != p);
}
 
int keys[10] = {1, 2, 3, 4, 5, 6, 7, 9, 10, 11};
 
int main() {
    FibHeap * heap = NULL;
    FibNode * x = NULL;
    heap = FibHeapMake();
    FibHeapInsertKeys(heap, keys, 10);
    FibHeapPrint(heap);
 
    x = FibHeapExtractMin(heap);
    printf("抽取最小值%d之后:\n", x->key);
    FibHeapPrint(heap);
 
    x = FibHeapSearch(heap, 11);
    if (NULL != x) {
        printf("查找%d成功,", x->key);
        FibHeapDecrease(heap, x, 8);
        printf("减小到%d后:\n", x->key);
        FibHeapPrint(heap);
    }
 
    x = FibHeapSearch(heap, 7);
    if (NULL != x) {
        printf("删除%d成功:\n", x->key);
        FibHeapDelete(heap, x);
        FibHeapPrint(heap);
    }
 
    FibHeapDestory(heap);
    return 0;
}


  

 4.参考:

ITEYE(代码):http://dsqiu.iteye.com/blog/1714961

 

5.神奇的斐波那契数列

更有意思的是神奇的斐波那契数列:1,1,2,3,5,8,13,……

wiki百科:http://zh.wikipedia.org/zh/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97

baidu百科:http://baike.baidu.com/view/816.htm

你可能感兴趣的:(Introduction,to,Algorithms,数据结构)