3.2 二项堆

  二项堆和二叉堆都是是为了优先队列的功能。

1 结构

  一个二项堆由一组二项树组成。
  二项树的特性是,其中一棵树的根是另一棵树的最左孩子。画成图就是这样
在这里插入图片描述

  二项树的性质,要牢记啊:
  1 拥有 2 k 2^k 2k个节点
  2 树的高度为k
  3 在深处d处恰有 ( d k ) (^k_d) (dk)个节点。这是数学里组合符号,代表从k中选d个的组合数量,哈哈,学过排列组合就知道了。
  4 根的度数为k,子女的度数(高度)依次递减。
  那为什么叫二项树。这里面有数学之美。
在这里插入图片描述

  可以看到这两部分完美对称。
  每一个都完美对称,可以一直二分下去。
  从上面的完美对称性质可以看到。节点的个数只能是2的n次方。
  但是实际应用中,节点的数量不可能这么完美啊。怎么办呢?这就需要一组二项树。比如13个节点,可以表示为8+4+1.这其实就是二进制计算。13转为二进制是1101.
  13个元素的二项堆如下图
在这里插入图片描述

  二项堆的性质:
  1 每棵树都是小顶堆
  2 按二进制编码决定树的数量。
  3 按树的高度在链表上排序
  每个二项树的节点有五个属性:
  如图所示:
在这里插入图片描述

  这五个属性是
  1 p
  2 key
  3 degree
  4 child
  5 sibling
  所以二项堆只有两个类。一个是堆类,只包含了一个head节点。一个是节点类。

2 合并两个二项堆

  二项堆的算法里面最核心的算法,比较难的算法。这个过程分为两步,第一步是合并(MERGE),第二步是进行连接(UNION)。
  合并两个链表的过程是新建一个二项堆h,每次从两个二项堆链上最左侧取最小高度的节点,拼接到h上。
  合并链表之后要对链表上同高度的节点继续进行连接。
  这时候涉及四种场景:
  1. 当前指针和下一个节点等级不相等
  2. 当前指针和下下个指针高度相等
  3. 当前指针key<=下一个指针的key
  4. 排除以上所有情况
  代码比较繁琐,这就和红黑树代码一样,各种case,各种if/else,代码很丑,但是没有办法通过一个优雅的判断去实现。当然这比红黑树代码的场景还是少一点的。
  Case 2、3、4都是等级/高度相等的。但是稍有不同
  Case 2是三个树连续高度相等,但是当前指针x在第一个。这个时候如果合并,前面的树高度就比后面的高了,不符合二项树的特征了。所以直接把x指针移动一格。转为case 3或case 4的场景。
  Case 3肯定是和下一棵树高度相等,也可能是三棵树高度相等,但是x指向中间那棵。  Case 3和case 4的区别在于当前节点比下一个节点key值小。
  Case 4 是在于当前节点比下一个节点key值大,其他的和case 3一样。
  所以case 3 和case 4是需要合并节点的。
  图的话,是这样的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  可以看到case 2为什么跳过去呢?因为直接转变为case3 或case4了。
  两个同高度的节点合成一个节点的过程叫做链接(LINK)。

3 插入

  插入操作是临时创建一个只有一个节点的二项堆,然后调用两个堆的合并方法。

4 移除最小值

  步骤如下:
  1 找到最小值所在的链表节点x
  2 删除这个节点x
  3 反向添加x的children为新的二项树,反向的原因是因为二项树的高度是递减的,而链表的高度是递增的
  4 合并UNION新旧二项堆

5 减小键值

  下图是减小键值为7的过程。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  减小键值的过程比较简单,是一个数据结构不变,不断向上交换的过程。

6 移除任意值

  分为两步:
  1 替换为负无穷
  2 调用移除最小值方法
  代码如下:

7 python代码实现

  所有代码git地址
  https://e.coding.net/buildt/python-study/heaps.git
  完整代码,节点类:


# _*_ coding:utf-8 _*_

class BinomialNode:

    def __init__(self, value):
        self.__p = None
        self.__degree = 0
        self.__key = value
        self.__child = None
        self.__sibling = None
        self.__prev = None

    @property
    def p(self):
        return self.__p

    @p.setter
    def p(self, p):
        self.__p = p

    @property
    def degree(self):
        return self.__degree

    @degree.setter
    def degree(self, degree):
        self.__degree = degree

    @property
    def key(self):
        return self.__key

    @key.setter
    def key(self, key):
        self.__key = key

    @property
    def child(self):
        return self.__child

    @child.setter
    def child(self, child):
        self.__child = child

    @property
    def sibling(self):
        return self.__sibling

    @sibling.setter
    def sibling(self, sibling):
        self.__sibling = sibling
        if sibling is not None:
            sibling.__prev = self

    @property
    def prev(self):
        return self.__prev

    @prev.setter
    def prev(self, prev):
        self.__prev = prev

    # 两棵树的相加
    def __add__(self, other):
        if self.degree != other.degree:
            raise RuntimeError('两棵高度不一样的树不能相加')

        left, right = (self, other) if self.__key > other.__key else (other, self)
        left.p = right
        left.sibling = right.child
        right.child = left
        right.degree = right.degree + 1
        return right

    def children(self):
        x = self.child
        if x is None:
            return []
        result = [x]
        while x.sibling is not None:
            result.append(x.sibling)
            x = x.sibling
        return result

    def decrease_key(self, key):
        if key > self.__key:
            raise RuntimeError('不能增加键值')

        self.__key = key
        y = self
        z = y.p
        while z is not None and y.key < z.key:
            # 交换值
            y.key, z.key = z.key, y.key
            y = z
            z = z.p

    def __str__(self):
        return str(self.__key)

  堆类:


# _*_ coding:utf-8 _*_

# 一个二项堆底层是二项树的链表
from com.youngthing.heaps.binomial_node import BinomialNode


class BinomialHeap:

  def __init__(self, *args):
      if args is None or len(args) == 0:
          self.__head = None
      else:
          value = args[0]
          self.__head = BinomialNode(value)

  # 查找最小值,只需要在根部的链表查询就可以了
  def min_key(self):
      min = self.min_node()
      return min.key

  def min_list_node(self):
      point = self.__head
      min = self.__head
      while point is not None:
          if point.key < min.key:
              min = point
          point = point.sibling
      return min

  def min_node(self):
      return self.min_list_node().key

  # 二项堆最重要的操作就是合并,合并的重要方向是合并同样degree的。
  def __add__(self, other):
      h = self.merge(other)
      if h is None:
          return h
      x = h.__head
      while x.sibling is not None:
          # case 1
          if x.degree != x.sibling.degree:
              x = x.sibling
          # case 2
          elif x.sibling.sibling is not None and x.sibling.sibling.degree == x.degree:
              x = x.sibling
          elif x.key <= x.sibling.key:
              # 让下一个消失,直接连上下下个
              x_next = x.sibling
              x.sibling = x_next.sibling
              x_next.sibling = None
              x_next.prev = None
              # 和下一个相加
              x = x + x_next
          else:
              # 当前节点是要“消失”在链上的
              # 这里做个防空判断
              x_prev = x.prev
              x_sibling = x.sibling
              x.prev = None
              x.sibling = None
              x = x + x_sibling
              if x_prev is None:
                  h.__head = x_sibling
                  h.__head.prev = None
              else:
                  x_prev.sibling = x_sibling
      return h

  def insert(self, value):
      h = BinomialHeap(value)
      self.__head = (self + h).__head

  def extra_min(self):
      if self.__head is None:
          raise RuntimeError('二项堆为空')

      # 如果只有一个,那么直接删除
      if self.__head.sibling is None and self.__head.degree == 0:
          x = self.__head
          self.__head = None
          return x

      x:BinomialNode = self.min_list_node()
      # 链上删除掉x
      if x.prev is not None:
          x.prev.sibling = x.sibling
      else:
          self.__head = x.sibling
          if x.sibling is not None:
              x.sibling.prev = None
      # 清空属性
      x.sibling = None
      x.prev = None
      # 逆向添加x所有的children
      children = x.children()
      h = BinomialHeap()
      for i in range(len(children) - 1, -1, -1):
          k = children[i]
          k.sibling = None
          k.prev = None
          k.p = None
          h.append(k)

      self.__head = (self + h).__head
      return x.key

  # 合并
  def merge(self, other):
      # 首先新建一个新的堆
      h = BinomialHeap()
      # 然后通过链表将两个链表连起来
      # 具体算法就是循环遍历两个链表,然后串起来
      # 双指针定义
      p1: BinomialNode = self.__head
      p2: BinomialNode = other.__head
      while p1 is not None and p2 is not None:
          if p1.degree < p2.degree:
              x = p1.sibling
              p1.sibling = None
              h.append(p1)
              p1 = x
          elif p1.degree == p2.degree:
              x = p1.sibling
              y = p2.sibling
              p1.sibling = None
              p2.sibling = None
              h.append(p1).append(p2)
              p1 = x
              p2 = y
          else:
              x = p2.sibling
              p2.sibling = None
              h.append(p2)
              p2 = x
      # 然后连不空的
      if p1 is not None:
          h.append(p1)
      elif p2 is not None:
          h.append(p2)

      # 最后合并
      return h

  def append(self, node: BinomialNode):
      head = self.__head
      if head is None:
          self.__head = node
          return self
      # 只要不成环就行
      last = head
      while last.sibling is not None:
          last = last.sibling
      last.sibling = node
      return self

  def append_key(self, key):
      node = BinomialNode(key)
      return self.append(node)

  def delete(self, key):
      key.decrease_key(float('-inf'))
      self.extra_min()

  def __str__(self):
      is_first = True
      result_str = ''
      x = self.__head
      while x is not None:
          if is_first:
              is_first = False
          else:
              result_str += ' -> '
          result_str += str(x.key)
          x = x.sibling
      return result_str
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醒过来摸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值