zoukankan      html  css  js  c++  java
  • 随便说说堆——二项堆概念与实现

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

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

    • 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 }

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

  • 相关阅读:
    jmeter测试mysql数据库之JDBC请求
    接口测试浅谈
    python import xxx 与 from xxx import xx 模块引入的区别
    交互模式下测试python代码及变量的四则运算
    python入门之一python安装及程序运行
    zabbix命令之:zabbix_get命令
    snmpwalk工具使用
    zabbix基础知识
    Centos7.5 rpm安装zabbix_agent4.0.3
    mysql常用命令
  • 原文地址:https://www.cnblogs.com/pullself/p/10171580.html
Copyright © 2011-2022 走看看