随便说说堆——二项堆概念与实现

二项堆其实就是由多棵二项树构成的森林,每棵树的根结点构成一个单链表。其链表序列,按照每棵二项树的度从小到大排列。

依然分析它的五种基本操作:

  • make_heap:建立一个空堆,或者把数组中元素转换成二叉堆。
  • insert:插入元素。
  • minimun:返回一个最小数。
  • extract_min:移除最小结点。
  • union:合并堆

二项树

说说二项树。

定义

  • 二项树B0由一个结点组成。
  • 二项树Bk是由两棵二项树Bk-1组成,其中一棵树作为另一颗树的左儿子。

性质

  • 二项树Bk具有2k个结点。
  • 二项树Bk高度为k。
  • 二项树Bk在深度i处有Cki个结点。
  • 根的度数为k。

图为一颗B3


二项堆

说说二项堆,二项堆作为一个二项树的集合,按照根结点的度数大小从小到大排列,每棵二项树都满足堆的性质(以下以最小堆性质为例),同时二项堆里不能有两棵或以上具有相同度数的二项树。

形如:

对每个结点做如下定义: 

1 typedef struct node {
2     int key;//键值
3     int degree;//度数
4     node *child;//第一个儿子
5     node *parent;//父结点
6     node *next;//兄弟结点
7 } bnode, *bheap;

 


基本操作

make_heap

建立新堆只需要分配一个二项堆的结点。生成一个堆对象便可。所以时间复杂度为O(1)。 

 1 bnode *Make_Node(int key) {
 2     bnode *node;
 3     node = new bnode;
 4     node->key = key;
 5     node->degree = 0;
 6     node->parent = nullptr;
 7     node->child = nullptr;
 8     node->next = nullptr;
 9     return node;
10 }

 

union

合并两个二项堆。时间复杂度为O(logn)

  • 将两个堆的根节点链表按照二项树度数从小到大重新组合。
  • 然后从左至右开始将相同度数的二项树合并,合并分为四种情况。
    • 当前结点和下一结点的度数不等时,不进行操作向后观察。
    • 当前结点、下一结点和下二结点度数都相等,暂时不进行处理,继续向后观察,这是为了从最后的两个相同度数的树处理。
    • 当前结点度数与下一结点度数相等、下一结点度数与下二结点度数不等的时候,并且当前结点的键值小于等于下一结点的键值,此时,将下一结点作为当前结点的第一个儿子。
    • 当前结点度数与下一结点度数相等、下一结点度数与下二结点度数不等的时候,并且当前结点的键值大于下一结点的键值,此时,将当前结点作为下一结点的第一个儿子。

图示

代码实现

 1 //合并两个根节点链表
 2 bnode *Merge(bheap h1, bheap h2) {
 3     if (h1 == nullptr) return h2;
 4     else if (h2 == nullptr) return h1;
 5     bnode *head = nullptr;
 6     if (h1->degree < h2->degree) {
 7         head = h1;
 8         head->next = Merge(h1->next, h2);
 9     } else {
10         head = h2;
11         head->next = Merge(h2->next, h1);
12     }
13     return head;
14 }
15 
16 //将两个相邻的根树合并
17 void Link(bheap child, bheap heap) {
18     child->parent = heap;
19     child->next = heap->child;
20     heap->child = child;
21     heap->degree++;
22 }
23 
24 //合并
25 bnode *Union(bheap h1, bheap h2) {
26     bnode *heap;
27     bnode *pre_x, *x, *nxt_x;
28     heap = Merge(h1, h2);
29     if (heap == nullptr) return nullptr;
30     pre_x = nullptr;
31     x = heap;
32     nxt_x = x->next;
33     while (nxt_x != nullptr) {
34         if ((x->degree != nxt_x->degree)
35             || ((nxt_x->next != nullptr)
36                 && (nxt_x->degree == nxt_x->next->degree))) {
37             pre_x = x;
38             x = nxt_x;
39         } else if (x->key <= nxt_x->key) {
40             x->next = nxt_x->next;
41             nxt_x->parent = x;
42             Link(nxt_x, x);
43         } else {
44             if (pre_x == nullptr) heap = nxt_x;
45             else pre_x->next = nxt_x;
46             Link(x, nxt_x);
47             x = nxt_x;
48         }
49         nxt_x = x->next;
50     }
51     return heap;
52 }

 

 

insert

有了合并操作,插入操作也就很好实现了,只需要将新加入的结点与原二项堆合并即可。时间复杂度等同于union,为O(logn)

代码实现

1 bnode *Insert(bheap heap, int key) {
2     bnode *node;
3     node = Make_Node(key);
4     if (node == nullptr) return heap;
5     return Union(heap, node);
6 }

 

minimun

对于二项堆,其最小结点必然在根链表中,所以只要遍历一遍根链表就可以找到。根链表由logn个结点组成,所以时间复杂度为O(logn),如果在其他操作中设置一个指针反复更新最小结点位置,那么时间复杂度可以压缩至O(1)。

代码实现

1 bnode *Minimun(bheap heap) {
2     bnode *pos = heap;
3     bnode *minn = heap;
4     while (pos != nullptr) {
5         if (pos->key < minn->key) minn = pos;
6         pos = pos->next;
7     }
8     return minn;
9 }

 

extract_min

移除最小结点,因为最小结点必然在根链表中,所以此操作可以:

  • 将最小根结点的二项树从原二项堆中移除。
  • 将此二项树根结点删除。
  • 将此根结点的儿子链表作为新的根链表构成新的二项堆。注意二项堆中二项树需按照度数从小到大排列。
  • 然后将这个二项堆与原二项堆合并。

时间复杂度O(logn)。

图示

                          

代码实现

 1 //将子树们转换成一个新的二项堆
 2 bnode *Transform(bheap heap) {
 3     bnode *next;
 4     bnode *tail = nullptr;
 5     if (!heap) return heap;
 6     heap->parent = nullptr;
 7     while (heap->next) {
 8         next = heap->next;
 9         heap->next = tail;
10         tail = heap;
11         heap = next;
12         heap->parent = nullptr;
13     }
14     heap->next = tail;
15     return heap;
16 }
17 
18 //删除最小堆顶元素
19 bnode *Extract_Min(bheap heap) {
20     bnode *minnode = Minimun(heap);
21     bnode *pre, *pos;
22     pre = nullptr;
23     pos = heap;
24     while (pos != minnode) {
25         pre = pos;
26         pos = pos->next;
27     }
28     if (pre) pre->next = minnode->next;
29     else heap = minnode->next;
30     heap = Union(heap, Transform(minnode->child));
31     delete (minnode);
32     return heap;
33 }

 

 


最后一想,二项堆其实也就在合并上比之二叉堆来的优秀。但是在二项堆相似的思想上可以继续优化,下一篇再随便说说斐波那契堆。

posted @ 2018-12-24 23:14  水喵桑  阅读(913)  评论(0编辑  收藏  举报