这三篇文章主要讲解二项堆、斐波那契堆、Pairing 堆,这三种结构主要用于优先队列的实现。资料主要参考《算法导论》和或互联网。
《算法导论》第19章讲到了二项堆
二项堆是二项堆是由一组二项树组成,在给出二项堆的定义之前,首先我们来定义什么是二项树。
二项树是一种递归的有序树。定义如下:
1. 二项树B[0]仅仅包含一个节点
2. B[k]是由两棵B[k-1]二项树组成,其中一颗树是另外一颗树的左孩子。
下面(a)是二项树B[k]递归定义,(b)二项树B[0]到B[4],(c)看待二项树的另一种方式:
二项树的性质:
1. 对于树B[k]该树含有2^k个节点;
2. 树的高度是k;
3. 在深度为i中含有Cik节点,其中i = 0, 1,2 ... , k;
4. 根的度数为k,最大,
推论:n节点的二项树,任意节点的最大度数为log(n)
二项堆是由一组满足下面的二项树组成:
1. H中的每个二项树遵循最小堆性质(类似小顶堆);
2. 对于任意的整数k的话,在H只存在一个度数是k的二项树;
另外定义:
1. 二项堆的度数定义成子女个数;
2. 定义二项堆的根表是二项树的根节点形成的链表;
了解了什么二项堆的定义和使用场景之后,我们来看看如何存储二项堆?
首先定义二项堆中每个节点的类型:
1. parent:指向父节点
2. sibling:指向右边的兄弟节点
3. child:定义该节点的子节点
4. degree:定义该节点的度数
5. 其他应用场景中需要的数据
struct heap_node
{
struct heap_node*parent;
struct heap_node*next;
struct heap_node*child;
unsigned intdegree;
void*value;
struct heap_node**ref;
};
定义二项堆数据类型:
1. 根表的头节点
2. 其他应用需要的数据
struct heap {
struct heap_node*head;
struct heap_node*min;
};
MAKE-BINOMIAL-HEAP,
分配并返回一个对象H,head[H]=NIL;running time is(1).
仅仅将根表的头节点指向null:
}
BINOMIAL-HEAP-MINIMUM,
返回一个指针,指向最小关键字节点。
由于二项堆中每个二项树都是遵循最小堆性质的,所以最小元素一定是在根表中,遍历根表一次即可找出最小元素:
BINOMIAL-HEAP-MINIMUM(H)
1 y NIL
2 x head[H]
3 min
4 while x NIL
5 do if key[x] < min
6 then min key[x]
7 y x
8 x sibling[x]
9 return y
BINOMIAL-HEAP-UNION
二项堆的合并操作是一个比较复杂,但是至关重要的一个过程,这里假设需要合并的两个二项堆是H1和H2,如果简单的将H1和H2的根表(两个链表)进行合并的话,显然这可能是违反了二项堆定义的第二条:
2. B[k]是由两棵B[k-1]二项树组成,其中一颗树是另外一颗树的子树。
这就是二项堆合并操作的主要需要解决的问题:两个二项堆合并完成之后,可能在根表中存在两个度数相同的节点,需要将度数相同的节点合并成一个新的节点。
这里我们进一步将这个问题现在转换成了:已知H1根表和H2的根表中每个节点的度数,并且H1和H2的根表已经是按照度数排序的,如何H1和H2根表合并,并且新的根表中不存在两个度数相同的节点。
一个朴素的思想就是现将H1和H2合并,然后在进行适当的调整,将两个度数相同的节点合并掉,这样保证了新的根表中不存你在两个度数相同的节点。那么基本的程序框架:
union(H1, H2)
{
#1:合并H1和H2的根表,生成新的根表H,并且H的节点是按照度数排序的;
#2:将H中度数相同的两个节点合并,直到根表H中不存在两个度数相同的节点;
}
继续细化上面的思路,#1是比较简单的,类似于归并排序中的合并的思路,但是对于#2而言,由于H是排序完成的,那么我们仅仅需要将H遍历一遍,如果存在两个相同的度数,合并即可。
case1: 当前节点的degree[x]与其后继结点的degree[next-x]不相等;后移;
case2: 当前节点的degree[x]与其后继的后继degree[sibling[next-x]]相等;后移;(三个深度都一样)
else:
degree[x] = degree[next-x]:
case3: if(key[x] <= key[next-x]) : sibling[x] = sibling[next-x], BINOMIAL-LINK(next-x,x)//next-x赋给x的左孩子
case4: if(key[x] > key[next-x]) : sibling[prev-x] = next-x, BINOMIAL-LINK(x,next-x)//x赋给next-x的左孩子
BINOMIAL-HEAP-INSERT(H,x)
首先构建一个只包含一个元素的二项堆H',O(1),再合并,O(log(n));
1 H' MAKE-BINOMIAL-HEAP()
2 p[x] NIL
3 child[x] NIL
4 sibling[x] NIL
5 degree[x] 0
6 head[H'] x
7 H BINOMIAL-HEAP-UNION(H,H')
BINOMIAL-HEAP-EXTRACT-MIN
1 find the root x with the minimum key in the root list of H, MAKE-BINOMIAL-HEAP
and remove x from the root list of H
2 H' MAKE-BINOMIAL-HEAP()
3 reverse the order of the linked list of x's children,
and set head[H'] to point to the head of the resulting list 逆向连接x的孩子到head[H']
4 H BINOMIAL-HEAP-UNION(H,H')合并
5 return x
BINOMIAL-HEAP-DECREASE-KEY (H,x,k)
将y减小到7,冒泡上升即可。
BINOMIAL-HEAP-DELETE(H,x)
将删除元素当做无穷小,利用 BINOMIAL-HEAP-DECREASE-KEY (H,x,k)冒泡上升到树根,再删除BINOMIAL-HEAP-EXTRACT-MIN(H)
1 BINOMIAL-HEAP-DECREASE-KEY(H,x,-)
2 BINOMIAL-HEAP-EXTRACT-MIN(H)
我们在什么情况下使用“二项堆”?
如果是不支持所谓的合并操作union的话,普通的堆的数据结构就是一种很理想的数据结构。 但是如果想要支持集合上的合并操作的话,最好是使用二项堆或者是斐波那契堆,普通的堆在union操作上最差的情况是O(n),但是二项堆和斐波那契堆是O(lgn)。
Binary heap Binomial heap() Fibonacci heap
二叉堆(最坏情况) 二项堆(最坏情况) (斐波那契堆(平摊))
Procedure (worst-case) (worst-case) (amortized)
--------------------------------------------------------------
MAKE-HEAP (1) (1) (1)
INSERT (lg n) O(lg n) (1)
MINIMUM (1) O(lg n) (l)
EXTRACT-MIN (lg n) (1g n) O(lg n)
UNION (n) O(lg n) (1)
DECREASE-KEY (lg n) (lg n) (1)
DELETE (1g n) (lg n) O(lg n)
算法导论:http://net.pku.edu.cn/~course/cs101/2007/resource/Intro2Algorithm/book6/chap20.htm
博客园(内含代码):http://www.cnblogs.com/xuqiang/archive/2011/06/01/2065549.html
ITEYE(完整代码):http://dsqiu.iteye.com/blog/1714961