跟我一塊兒學算法——二項堆

1.二叉堆(Binary Heap)、二項堆、斐波那契堆(簡稱Fib堆)的比較:

相同:python

  1. 都是可歸併堆(Mergeable Heap);
  2. 它們都支持5個基本操做(建立、插入、查找最小值、抽取最小值、合併堆)和2個擴展操做
    (結點減值、結點刪除)。

不一樣:算法

  1. 二叉堆是一種結點有序的徹底二叉樹,可採用數組結構存儲,經過數組下標索引結點,分最大
    堆和最小堆。 二項堆和Fib堆都是最小堆。
  2. 二項堆由二項樹組成,結構比二叉堆複雜,但其堆合併操做的時間複雜度較好。當堆合併操做
    較多時,可以使用二項堆。反之,使用二叉堆便可。
    在這裏插入圖片描述

2. 二項樹

2.1 定義

僅包含一個結點的有序樹是一棵二項樹(B_0樹)。二項樹B_k由兩棵B_{k-1}樹組成,其中一
棵B_{k-1}樹的根做爲另外一棵B_{k-1}樹根的最左孩子(k≥0)。
在這裏插入圖片描述數組

2.2 二項樹B_k的性質

  1. 結點數 n = 2{^k}
  2. 樹高爲 k = lgn
  3. 深度爲i處有k!/(i!(k-i)!)個結點(k>=i>=0)。
  4. 根的度最大爲k,若根的孩子從左到右編號爲k-1,k-2,…,1,0,則孩子i剛好是子樹B_i的根。

proof:主要依靠B_k與B_{k-1}間的關係數據結構

  1. 2^{k-1} + 2^{k-1} = 2^{k}
  2. k-1+1 = k
  3. 即證D(k,i) = D(k-1,i-1) + D(k-1,i)
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-x0BE2UNK-1582727656770)(img/binomialHeap-math1.png)]

3. 二項堆

3.1 定義

它是由一系列二項樹組成的集合,知足如下性質:
堆中每一顆二項樹都知足最小堆性質。堆中度爲k的樹是惟一的 => n個結點的二項堆中最多有
lgn上界 + 1課二項樹app

3.2 數據結構

  • 根表 root list
    head[H]->B_0->B_2->B_3
    根表是單鏈表,它連接全部二項樹的根結點,且按度的遞增順序連接。ide

  • 結點 node
    每一個結點包含5個域:
    key:數據
    指針p:指向父結點
    degree(度):孩子個數
    child:指向最左孩子
    sibling:指向右兄弟
class Node():
  """
  class of the node in the heap
  provide functions to the binomial tree
  """
  def __init__(self, key = None):
      self.p = None # point to parent
      self.key = key # value
      self.degree = 0 # count of the children
      self.child = None # point to child of the left
      self.sibling = None # point to the right brother

  def link_tree(self, other):
      """
      other -> subtree of self.
      """
      other.parent = self
      other.sibling = self.child
      self.child = other
      self.degree += 1

3.3 操做

3.3.1 五個基本操做

  1. 建立空堆
  2. 取最小值:因爲二項樹知足最小堆性質,因此遍歷根表便可。
  3. 合併兩個二項堆
    step1:按照二項樹的度遞增的順序合併兩個根表。
    step2:根表調整,以知足度的惟一性。用三個輔助指針(per、p、after)將度重複的樹合併。
    因爲step1合併後的根表中,度相同的樹最多有兩顆,因此會出現如下幾種狀況:
    case1:三個指針所指二項樹根都存在,且度不一樣 => 指針後滑,進入case3或結束。
    case2:per爲空,p.degree = after.degree,且after.sibling存在 =>指針後滑,進入case4。
    case3:case1或case2不成立,若pre爲空,則必定有p.degree = after.degree => 根據degree
    合併p和after所指二項樹,after後滑,進入case2 或 case1
    case4:三個指針所指二項樹根都存在,且度相同 =>根據degree合併p和after所指二項樹,after
    後滑,進入case3。this

    時間複雜度分析:
    • 合併根表 O(lgn)
    • 根表調整,遍歷新根表O(lgn)
    • 合併操做的時間複雜度爲O(lgn),優於二叉堆的O(n)

    在這裏插入圖片描述

    def _merge_rootlist(self, heap2):
         """
             merge  two root list and keep increasing order in degree.
         """
         p1 = self.head
         p2 = heap2.head
         if not p1: # p1 = None
             return heap2.head
         if not p2: # p2 = None
             return self.head
         if p1.degree <= p2.degree:
             p = p1
             p1 = p1.sibling
         else:
             p = p2
             p2 = p2.sibling
         head = p
         while p1 and p2:
             if p1.degree <= p2.degree:
                 p.sibling = p1
                 p1 = p1.sibling
             else:
                 p.sibling = p2
                 p2 = p2.sibling
             p = p.sibling
         if p2:
             p.sibling = p2
         else:
             p.sibling = p1
         return head
    def _union(self, heap2):
       """
           step1: merge  two root list and keep increasing order in degree.
           step2: adjust root(merge) to keep the unique of the degree in all   
           binomial trees.
           use three point to adjust the heap: pre , p , after
       """
       if heap2 is None:
           return
       if self.head is None:
           self.head = heap2.head
           self.size = heap2.size
           return
       # step1
       head = self._merge_rootlist(heap2)
       print("merge root list")
       self.print_rootlist()
       # step2 use three point to adjust the heap
       if not head:
           print("merge rootlist error")
           return
       pre = None
       p = head
       after = head.sibling
       while after:
           # case 1 / case 2 , point + 1
           if p.degree != after.degree or (after.sibling is not None and
           after.sibling.degree == p.degree ):
               pre = p
               p = after
           # case 3,  merge p and after into p
           elif p.key <= after.key:
               # update point
               p.sibling = after.sibling
               # merge two tree, p.child = after
               p.link_tree(after)
           else:
               # after.degree == p.degree, after.sibling = None, p.key>after.key
               # => update head ,link(after,p),over!
               if pre == None:
                   head = after
               # upfate pre.sibling = after, link(after,p)
               else:
                   pre.sibling = after
               after.link_tree(p)
               p = after
           after = p.sibling
       self.head = head
       self.size += heap2.size
       return
  4. 插入結點x
    將x放入一個空堆H2中,將H和H2合併。
    時間複雜度爲O(lgn)
def insert(self, node):
    """
    insert a node into a null heap.
    1. node->new heap (heap2)
    2. union(self, heap2)
    """
    h = BinomialHeap()
    h.head = node
    self.union(h)
    self.size += 1
  1. 抽取最小值結點
    step1:遍歷根表查最小值結點z。
    step2:在根表中刪除結點z,並把z的孩子"逆放"到一個空堆H2中。所謂逆放,即便H2知足二項堆根
    表中樹根的度遞增的順序。
    step3:將H和H2合併。
    時間複雜度O(lgn)
def extract_min_node(self):
        self._extract_min_node()
        return

def _extract_min_node(self):
    size = self.size
    min_node, pre_min = self.min()
    self.extract(min_node, pre_min)
    self.size = size - 1
    return

def extract(self, node, pre_node):
    if node == None:
        return
    # del min node in the root list
    if pre_node==None:
        self.head = min_node.sibling
    else:
        pre_node.sibling = node.sibling
    # if the minimum node has no child
    if(node.child == None):
        return
    # if the node has subtrees, then inesrt them into a new heap, and union this new heap with old heap.
    new_heap = BinomialHeap()
    # insert the subtrees in reverse order
    p = node.child
    list_root = []
    while p.sibling != None:
        p.parent = None
        list_root.append(p)
        p = p.sibling
    list_root.append(p)
    while list_root != []:
        p = list_root.pop(-1)
        new_heap.insert(p)
    # union
    self.union(new_heap)
    return

3.3.2 兩個擴展操做

  1. 減值(減小結點z的key)
    z減值後自底向上迭代比較,直到孩子結點的值大於父結點。相似冒泡。
    時間複雜度;O(lgn)
def _decrease_key(self,node,key):
        if node == None or node.key <= key:
            print("node or key err")
            return
        node.key = key
        x = node
        p = node.p
        # bubble
        while p is not None and p.key > x.key:
            t = p.key
            p.key = x.key
            x.key = t
            x = p
            p = p.p
        return
  1. 刪除結點z
    step1:對z進行減值操做,將z的值減爲最小值。
    step2:對z所在二項樹的樹根執行抽取操做。
    時間複雜度:O(lgn)

參考

《算法導論》指針

相關文章
相關標籤/搜索