libuv的最小堆heap代碼解讀

libuv是純c編寫的事件驅動的網絡庫, 它的定時器也是用最小堆實現(最小堆原理). 可是與java中的ScheduledThreadPoolExecutor/Timer中的最小堆不一樣, libuv使用鏈表形式來存放結點, 而不是數組.java

//節點信息
struct heap_node {
   //分別爲左子結點, 右子結點, 父結點的指針
  struct heap_node* left;
  struct heap_node* right;
  struct heap_node* parent;
};

//堆
struct heap {
  struct heap_node* min; //塔尖的位置的指針
  unsigned int nelts; //節點的數量
};

它使用鏈表的緣由可能有:node

  1. c語言的對象/接口封裝, 沒有java/c++那樣優雅, 不太好優雅地記錄節點在數組中的索引位置.
  2. 不須要預先分配內存, 不須要中途擴充數組

負做用: 插入新節點時, 須要先從塔尖位置開始找出最末尾的位置, libuv的末節點搜索過程須要循環2倍的塔層數;c++

若是搜索末尾節點?
當以數組來存儲最小堆時, 它有這樣的性質(首位置索引定爲1):
**第K個節點的左子節點位於2K的位置, 右子節點位於2K+1的位置, 父結點位於K/2的位置 **
左節點一定在偶數位置, 右節點一定在奇數位置.
而後根據上面的性質能夠依次推算出它的各級父結點的位置以及左右屬性
來看看libuv-1.x的入列過程數組

//使用一個int值來保存末節點的各級父節點的左右關係, 0表示在左邊, 1表示在右邊
  //最後一個bit爲金字塔第二級的左右關係
  int path = 0;
  //此處代碼是以1做爲首位置的索引, 那麼左節點的索引爲偶數, 右節點的索引爲奇數
  //父結點的索引爲K/2
  //1 + heap->nelts表示待填充的節點的索引.
  for (k = 0, n = 1 + heap->nelts; n >= 2 /*跳過塔尖*/; k += 1, n /= 2/*父節點的索引*/)
      //若是n爲偶數(左節點), 那麼(n & 1)值爲0, 反之(右節點)爲1; 而後將這個值存入path
    //k用來記錄path中的有效值的個數
    path = (path << 1) | (n & 1); 

  /* Now traverse the heap using the path we calculated in the previous step. */
  //從塔尖的位置一層一層的往下查找
  parent = child = &heap->min;
  while (k > 0) {
    parent = child;
    if (path & 1) //取出最後一個bit, 按照上面的推斷, 1表示右邊.
      child = &(*child)->right;
    else
      child = &(*child)->left;
    path >>= 1;
    k -= 1;
  }

/* Insert the new node. */
  newnode->parent = *parent;
  *child = newnode;
  heap->nelts += 1;

  /* Walk up the tree and check at each node if the heap property holds.
   * It's a min heap so parent < child must be true.
   */
   /** 若是父節點較大, 則執行上移過過程 */
  while (newnode->parent != NULL && less_than(newnode, newnode->parent))
    heap_node_swap(heap, newnode->parent, newnode);

heap_node_swap代碼解析:網絡

/* Swap parent with child. Child moves closer to the root, parent moves away. */
//爲了方便說明, 本文只考慮一個比較簡單的場景,如圖1
static void heap_node_swap(struct heap* heap,
                           struct heap_node* parent,
                           struct heap_node* child) {
  struct heap_node* sibling;
  struct heap_node t;

  //將child與parent指向的內容交換; 交換以後的關係, 如圖2
  t = *parent;
  *parent = *child;
  *child = t;

  //開始修復各類指針的指向, 如圖3
  parent->parent = child; //見圖3中的線(1),修復新子節點的parent
  if (child->left == child) { 
    child->left = parent;  修復新有parent的左子節點
    sibling = child->right;
  } else {
    child->right = parent; //見圖3中的線(2), 修復新有parent的右子節點
    sibling = child->left;
  }
  if (sibling != NULL)
    sibling->parent = child; //見圖3中的線(3)

   //本文暫未考慮這種狀況, 讀者能夠本身畫一畫
  if (parent->left != NULL)
    parent->left->parent = parent;
  if (parent->right != NULL)
    parent->right->parent = parent;

  if (child->parent == NULL)
    heap->min = child;
  else if (child->parent->left == parent)
    child->parent->left = child;
  else
    child->parent->right = child; //見圖3中的線(4)
}

圖1 less


圖2 .net


圖3 指針

相關文章
相關標籤/搜索