优先队列三大结构之一——二项堆

这三篇文章主要讲解二项堆、斐波那契堆、Pairing 堆,这三种结构主要用于优先队列的实现。资料主要参考《算法导论》和或互联网。

 

《算法导论》第19章讲到了二项堆

 

1.二项堆简介                                                                                

 

二项堆是二项堆是由一组二项树组成,在给出二项堆的定义之前,首先我们来定义什么是二项树。

1.1二项树

二项树是一种递归的有序树。定义如下:

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.2二项堆

二项堆是由一组满足下面的二项树组成:

1.  H中的每个二项树遵循最小堆性质(类似小顶堆);

2. 对于任意的整数k的话,在H只存在一个度数是k的二项树;

另外定义:

1. 二项堆的度数定义成子女个数;

2. 定义二项堆的根表是二项树的根节点形成的链表;

 

2.数据结构                                                                           

 

了解了什么二项堆的定义和使用场景之后,我们来看看如何存储二项堆?

首先定义二项堆中每个节点的类型:

1. parent:指向父节点

2. sibling:指向右边的兄弟节点

3. child:定义该节点的子节点

4. degree:定义该节点的度数

5. 其他应用场景中需要的数据

  1. struct heap_node  
  2. {  
  3. struct heap_node*parent;  
  4. struct heap_node*next;  
  5. struct heap_node*child;  
  6. unsigned intdegree;  
  7. void*value;  
  8. struct heap_node**ref;  
  9. };   
struct heap_node
{
struct heap_node*parent;
struct heap_node*next;
struct heap_node*child;
unsigned intdegree;
void*value;
struct heap_node**ref;
}; 


对于定义的字段,对于根表中的节点和非根表中的元素是不相同的,根表中的节点的parent全部是空,sibling指向的是根表中的下一个元素;对于非根表中的节点的话,parent指向的是该节点的父节点,sibling指向该节点的兄弟节点。

定义二项堆数据类型:

1. 根表的头节点

2. 其他应用需要的数据 

  1. struct heap {  
  2. struct heap_node*head;  
  3. struct heap_node*min;  
  4. };   
struct heap {
struct heap_node*head;
struct heap_node*min;
}; 

 

3.二项堆操作                                                                              

 

3.1创建一个新的二项堆

MAKE-BINOMIAL-HEAP,

分配并返回一个对象H,head[H]=NIL;running time is(1).

仅仅将根表的头节点指向null:

static inline void heap_init(struct heap* heap)
{
heap->head = NULL;
heap->min  = NULL;// 其他操作

3.2寻找最小关键字

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


3.3 合并操作(MOST)

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的左孩子

 

3.4 插入一个节点

 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')

3.5 抽取最小元素

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

 

 

 

3.6 减小一个关键字的值

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

将y减小到7,冒泡上升即可。

3.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)

 

4.应用情况

我们在什么情况下使用“二项堆”?

如果是不支持所谓的合并操作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)

 

5.参考资料

算法导论: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

 

原文: http://blog.csdn.net/sangni007/article/details/8141698
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值