1. 概念
二项堆:它是一组由二项树组成的结构。
1)二项树Bk是一棵具有以下性质的树(这是一个递归定义)[1]:
A: 共有2^k 个节点
B: 树的高度为k
C:在深度i处恰有个节点,其中i = 0, 1, 2, ... k
D: 根的度数为k,它大于任何其它节点的度数,并且如果根的子女从左到右编号为k - 1, k - 2, ..., 0, 子女i是子树Bi的根。
具体形式如图:
[2]
2) 二项堆由一组满足下面性质的二项树组成[3]:
A: 每个二项树遵循最小堆性质:节点的关键字大于或等于其父节点的关键字。
B: 对于任意的非负整数k,在二项堆中至多有一棵二项树的根具有度数k
具体形式如图:
[4]
上图(a)给出的二项堆H的head节点指向它所包含的的度数最小的一棵二项树,最小二项树再指向度数次最小的一棵二项树,同时我们会发现一个拥有n个节点的二项堆,它所拥有的二项树刚好是它的二进制表示形式。图(a)中共有13个节点,即 n = 13 =(1101),所以这个二项堆包含的二项树的度数分别是0,2,3,每个对应度数二项树包含的节点个数为2^0, 2^2, 2^ 3。
通过图(b)我们可得知,每棵二项树通过左孩子右兄弟方式进行链接。同时二项树的根作为二项堆的根列表,并通过兄弟链进行连接。
2. 数据结构
// basic structure of binomial heap node
typedef struct Node {
ElemType key;
int degree;
struct Node *p;
struct Node *child;
struct Node *sibling;
}Node;
// the structure of binomial heap
typedef struct heap{
Node * head;
}*BinomialHeap;
3. 寻找最小关键字
由最小堆性质,我们可以得出二项堆h的最小值,在根列表中查找
// find the minimum key from the binomial heap
// and return the minimum node's pointer
Node * binomialHeapMinimum(BinomialHeap h) {
Node * y = NULL;
Node * x = h->head;
if(x != NULL) {
int min = x->key;
y = x;
x = x->sibling;
while (x != NULL) {
if(x->key < min) {
min = x->key;
y = x;
}
x = x->sibling;
}
}
return y;
}
4. 基本操作
将以节点y为根的Bk-1树与以节点z为根的Bk-1树连接起来,并使得z成为y的父亲节点,最终合并成一棵Bk树。
// links the Bk-1 tree rooted at node y to the Bk-1 tree
// rooted at node z; that is, it makes z the parent of y.
// Node z thus becomes the root of a Bk tree
void binomialLink(Node * y, Node *z) {
y->p = z;
y->sibling = z->child;
z->child = y;
z->degree += 1;
}
5. 合并两个二项堆h1和h2
1)步骤
A: 将两个二项堆所有包含的二项树拆分,然后按度数递增的方式将所有的根列表进行串联,并创建一棵新的二叉树,使得其head节点指向这串联的根列表的第一个节点。
[5]
// merge the root lists of h1 and h2 into a single linked list
// that is sorted into monotonically increasing order.
Node * binomialMerge(BinomialHeap h1, BinomialHeap h2) {
Node * firstNode = NULL;
Node * p = NULL;
Node * p1 = h1->head;
Node * p2 = h2->head;
if (p1 == NULL || p2 == NULL) {
if(p1 == NULL) {
firstNode = p2;
} else {
firstNode = p1;
}
return firstNode;
}
if (p1->degree < p2->degree) {
firstNode = p1;
p = firstNode;
p1 = p1->sibling;
} else {
firstNode = p2;
p = firstNode;
p2 = p2->sibling;
}
while (p1 != NULL && p2 != NULL) {
if (p1->degree < p2->degree) {
p->sibling = p1;
p = p1;
p1 = p1->sibling;
} else {
p->sibling = p2;
p = p2;
p2 = p2->sibling;
}
}
if (p1 != NULL) {
p->sibling = p1;
} else {
p->sibling = p2;
}
return firstNode;
}
B:串连后,将其中有相同度数的二项树进行合并,并使之符合最小堆性质,也要修改其中有的二项堆的父亲指针(即node数据结构中的p)。
[6]
由上面的图我们可以看出case3和case1是需要游动的prev-x, x 和nex-x等指针的,剩下的情况就是degree[x] == degree[next-x]所以要尽心 Binomial heap link,但必须使得link后的二项树满足最小堆性质。
// unite heaps h1 and h2, returning the resulting heap.
BinomialHeap binomialHeapUnion(BinomialHeap *h1, BinomialHeap *h2) {
BinomialHeap h = makeBinomialHeap();
h->head = binomialMerge(*h1, *h2);
free(*h1);
*h1 = NULL;
free(*h2);
*h2 = NULL;
if (h->head == NULL) {
return h;
}
Node * prev = NULL;
Node * x = h->head;
Node * next = x->sibling;
while (next != NULL) {
if (x->degree != next->degree
|| (next->sibling != NULL
&& x->degree == next->sibling->degree)) {
// case 3 or case 2, just move forward
prev = x;
x = next;
} else if (x->key <= next->key) {
// x->key <= next->key
x->sibling = next->sibling;
binomialLink(next, x);
} else {
// x->key > next->key
if (prev == NULL) {
h->head = next;
} else {
prev->sibling = next;
}
binomialLink(x, next);
}
// change the next
next = x->sibling;
}
return h;
}
6. 插入一个节点
就是将新插入的节点单独作为一个二项堆h1和要插入的二项堆h进行union操作。
//insert node into the binomial heap
void binomialHeapInsert(BinomialHeap *h, Node * x) {
BinomialHeap h1 = makeBinomialHeap();
h1->head = x;
*h = binomialHeapUnion(h, &h1);
}
7.抽取最小关键字
也就是将值最小的关键字从二项堆中删除,并返回该节点。
步骤:
A:找到二项堆h中最小值的节点(在根列表中),注意:一定要修改此节点的前一个兄弟节点的sibling指针(即指向最小值节点sibling指向的节点)
B:将最小值节点的儿子节点们串联成一个新的二项堆h1
C:将h和h1union
Node * binomialHeapExtractMin(BinomialHeap *h) {
Node * p = (*h)->head;
Node * x = NULL;
Node * x_prev = NULL;
Node * p_prev = NULL;
ElemType min;
if (p == NULL) {
return p;
}
// find the root x with minimum key in the root list of h
x = p;
min = p->key;
p_prev = p;
p = p->sibling;
while (p != NULL) {
if(p->key < min) {
x_prev = p_prev;
x = p;
min = p->key;
}
p_prev = p;
p = p->sibling;
}
if (x == (*h)->head) {
(*h)->head = x->sibling;
} else if (x->sibling == NULL) {
x_prev->sibling = NULL;
} else {
x_prev->sibling = x->sibling;
}
Node * child_x = x->child;
if (child_x != NULL) {
// generate a new Binomial Heap
BinomialHeap h1 = makeBinomialHeap();
// reverse the order of linked list of x's children
// and set the children's p to NULL
// link h1->head to the head of resulting list
child_x->p = NULL;
h1->head = child_x;
p = child_x->sibling;
child_x->sibling = NULL;
while (p != NULL) {
p_prev = p;
p = p->sibling;
p_prev->sibling = h1->head;
h1->head = p_prev;
p_prev->p = NULL;
}
*h = binomialHeapUnion(h, &h1);
}
return x;
}
算法导论中图:
[7]
从(c)到(d)是进行了H和H'的union
8. 减小关键字的值
减小节点的关键字的值,表调整该节点在相应二项树中的位置。
// decrease the key of a node in a binomial heap h
// to a new value key, if key is greater than current value,
// throw an error
void binomialHeapDecreaseKey(BinomialHeap h, Node *x, ElemType key) {
if (x->key < key) {
printf("new key is greate than current key");
exit(-2);
}
x->key = key;
Node * y = x;
Node * p = x->p;
while (p != NULL && y->key < p->key) {
y->key = p->key;
p->key = key;
y = p;
p = y->p;
}
}
9. 删除一个关键字
将关键的值减小到无穷小后,并删除二项堆中的最小关键字
// delete a node x's key
void binomialHeapDelete(BinomialHeap *h, Node *x) {
binomialHeapDecreaseKey(h, x, -10000);
binomialHeapExtractMin(h);
}
10. 分析
从上面很容易得除了得到最小值,其它的操作都需要log(n)的时间
11. 详细源码
http://download.csdn.net/download/yzf0011/9757386
至此感谢 《算法导论》作者和此书,全文中若有参考遗漏的,请留言。
若有错误的地方请留言,感激不尽
参考
[1] Thomas H.Cormen、Charles E.Leiserson《算法导论》第二版 第十九章 “二项堆” p278
[2] Thomas H.Cormen、Charles E.Leiserson《算法导论》第二版 第十九章 “二项堆” p279
[3] Thomas H.Cormen、Charles E.Leiserson《算法导论》第二版 第十九章 “二项堆” p279
[4] Thomas H.Cormen、Charles E.Leiserson《算法导论》第二版 第十九章 “二项堆” p280
[5] Thomas H.Cormen、Charles E.Leiserson《算法导论》第二版 第十九章 “二项堆” p282~p283
[6] Thomas H.Cormen、Charles E.Leiserson《算法导论》第二版 第十九章 “二项堆” p283
[7] Thomas H.Cormen、Charles E.Leiserson《算法导论》第二版 第十九章 “二项堆” p287