前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >libuv源码阅读(3)--heap-inl.h

libuv源码阅读(3)--heap-inl.h

原创
作者头像
wanyicheng
修改2021-03-08 09:51:35
3760
修改2021-03-08 09:51:35
举报

这是一个按照完全二叉树逻辑组织的 完全二叉堆 实现:

整体源码:

代码语言:javascript
复制
#ifndef UV_SRC_HEAP_H_
#define UV_SRC_HEAP_H_

#include <stddef.h>  /* NULL */

#if defined(__GNUC__)
# define HEAP_EXPORT(declaration) __attribute__((unused)) static declaration
#else
# define HEAP_EXPORT(declaration) static declaration
#endif

struct heap_node {
  struct heap_node* left;
  struct heap_node* right;
  struct heap_node* parent;
};

/* A binary min heap.  The usual properties hold: the root is the lowest
 * element in the set, the height of the tree is at most log2(nodes) and
 * it's always a complete binary tree.
 *
 * The heap function try hard to detect corrupted tree nodes at the cost
 * of a minor reduction in performance.  Compile with -DNDEBUG to disable.
 */
struct heap {
  struct heap_node* min;
  unsigned int nelts;
};

/* Return non-zero if a < b. */
typedef int (*heap_compare_fn)(const struct heap_node* a,
                               const struct heap_node* b);

/* Public functions. */
HEAP_EXPORT(void heap_init(struct heap* heap));
HEAP_EXPORT(struct heap_node* heap_min(const struct heap* heap));
HEAP_EXPORT(void heap_insert(struct heap* heap,
                             struct heap_node* newnode,
                             heap_compare_fn less_than));
HEAP_EXPORT(void heap_remove(struct heap* heap,
                             struct heap_node* node,
                             heap_compare_fn less_than));
HEAP_EXPORT(void heap_dequeue(struct heap* heap, heap_compare_fn less_than));

/* Implementation follows. */

HEAP_EXPORT(void heap_init(struct heap* heap)) {
  heap->min = NULL;
  heap->nelts = 0;
}

HEAP_EXPORT(struct heap_node* heap_min(const struct heap* heap)) {
  return heap->min;
}

/* Swap parent with child. Child moves closer to the root, parent moves away. */
static void heap_node_swap(struct heap* heap,
                           struct heap_node* parent,
                           struct heap_node* child) {
  struct heap_node* sibling;
  struct heap_node t;

  t = *parent;
  *parent = *child;
  *child = t;

  parent->parent = child;
  if (child->left == child) {
    child->left = parent;
    sibling = child->right;
  } else {
    child->right = parent;
    sibling = child->left;
  }
  if (sibling != NULL)
    sibling->parent = child;

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

HEAP_EXPORT(void heap_insert(struct heap* heap,
                             struct heap_node* newnode,
                             heap_compare_fn less_than)) {
  struct heap_node** parent;
  struct heap_node** child;
  unsigned int path;
  unsigned int n;
  unsigned int k;

  newnode->left = NULL;
  newnode->right = NULL;
  newnode->parent = NULL;

  /* Calculate the path from the root to the insertion point.  This is a min
   * heap so we always insert at the left-most free node of the bottom row.
   */
  path = 0;
  for (k = 0, n = 1 + heap->nelts; n >= 2; k += 1, n /= 2)
    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)
      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_EXPORT(void heap_remove(struct heap* heap,
                             struct heap_node* node,
                             heap_compare_fn less_than)) {
  struct heap_node* smallest;
  struct heap_node** max;
  struct heap_node* child;
  unsigned int path;
  unsigned int k;
  unsigned int n;

  if (heap->nelts == 0)
    return;

  /* Calculate the path from the min (the root) to the max, the left-most node
   * of the bottom row.
   */
  path = 0;
  for (k = 0, n = heap->nelts; n >= 2; k += 1, n /= 2)
    path = (path << 1) | (n & 1);

  /* Now traverse the heap using the path we calculated in the previous step. */
  max = &heap->min;
  while (k > 0) {
    if (path & 1)
      max = &(*max)->right;
    else
      max = &(*max)->left;
    path >>= 1;
    k -= 1;
  }

  heap->nelts -= 1;

  /* Unlink the max node. */
  child = *max;
  *max = NULL;

  if (child == node) {
    /* We're removing either the max or the last node in the tree. */
    if (child == heap->min) {
      heap->min = NULL;
    }
    return;
  }

  /* Replace the to be deleted node with the max node. */
  child->left = node->left;
  child->right = node->right;
  child->parent = node->parent;

  if (child->left != NULL) {
    child->left->parent = child;
  }

  if (child->right != NULL) {
    child->right->parent = child;
  }

  if (node->parent == NULL) {
    heap->min = child;
  } else if (node->parent->left == node) {
    node->parent->left = child;
  } else {
    node->parent->right = child;
  }

  /* Walk down the subtree and check at each node if the heap property holds.
   * It's a min heap so parent < child must be true.  If the parent is bigger,
   * swap it with the smallest child.
   */
  for (;;) {
    smallest = child;
    if (child->left != NULL && less_than(child->left, smallest))
      smallest = child->left;
    if (child->right != NULL && less_than(child->right, smallest))
      smallest = child->right;
    if (smallest == child)
      break;
    heap_node_swap(heap, child, smallest);
  }

  /* Walk up the subtree and check that each parent is less than the node
   * this is required, because `max` node is not guaranteed to be the
   * actual maximum in tree
   */
  while (child->parent != NULL && less_than(child, child->parent))
    heap_node_swap(heap, child->parent, child);
}

HEAP_EXPORT(void heap_dequeue(struct heap* heap, heap_compare_fn less_than)) {
  heap_remove(heap, heap->min, less_than);
}

#undef HEAP_EXPORT

#endif  /* UV_SRC_HEAP_H_ */

实现分析:

代码语言:javascript
复制
// 初始化堆
HEAP_EXPORT(void heap_init(struct heap* heap));

// min指向最小元素(libuv定义的某种定时器结构体) nelts 堆中总元素的计数器
HEAP_EXPORT(void heap_init(struct heap* heap)) {
  heap->min = NULL;
  heap->nelts = 0;
}
代码语言:javascript
复制
// 取得最小元素的指针
HEAP_EXPORT(struct heap_node* heap_min(const struct heap* heap)) {
  return heap->min;
}
代码语言:javascript
复制
// 把 parent节点和child节点交换 child靠近根节点 parent远离根节点
static void heap_node_swap(struct heap* heap,
                           struct heap_node* parent,
                           struct heap_node* child) {
  struct heap_node* sibling;
  struct heap_node t;

  // 交换 2个 指针所分别指向的 某个heap_node结构体的地址 
  t = *parent;
  *parent = *child;
  *child = t;

  // 现在 parent 和 child 2个指针还是原来2个指针 但是它们通过 -> 引用的内容的却相反了 现在的 parent->xx 就是之前的child节点的xxx
  // 把原来child的parent指向现在新parent,即 child指针
  parent->parent = child;
  // 原parent的left如果是原来的child指针的话 说明 之前的child是之前的parent的左子节点
  if (child->left == child) {
    child->left = parent;
    sibling = child->right;
  } else {
    child->right = parent;
    sibling = child->left;
  }
  if (sibling != NULL)
    sibling->parent = child;

  // 原来的 child节点如果有左子节点 需要重新连接它的parent指向新的 child 指针 也就是 parent
  if (parent->left != NULL)
    parent->left->parent = parent;
  if (parent->right != NULL)
    parent->right->parent = parent;

  // 如果之前的parent 就是根节点 我们需要更新堆的min指针 让他重新指向新的根节点 child
  if (child->parent == NULL)
    heap->min = child;
  // 否则的话 重新建立 原 parent 的  parent 和新 parent 也就是 child指针的 联系
  else if (child->parent->left == parent)
    child->parent->left = child;
  else
    child->parent->right = child;
}

二叉堆的逻辑连接形式
二叉堆的逻辑连接形式
代码语言:javascript
复制
// 插入新的节点到堆中
HEAP_EXPORT(void heap_insert(struct heap* heap,
                             struct heap_node* newnode,
                             heap_compare_fn less_than)) {
  struct heap_node** parent;
  struct heap_node** child;
  unsigned int path;
  unsigned int n;
  unsigned int k;

  newnode->left = NULL;
  newnode->right = NULL;
  newnode->parent = NULL;

  /* Calculate the path from the root to the insertion point.  This is a min
   * heap so we always insert at the left-most free node of the bottom row.
   */
   
  path = 0;
  for (k = 0, n = 1 + heap->nelts; n >= 2; k += 1, n /= 2)
    path = (path << 1) | (n & 1);
    
  // 假设插入 child 最终得到的 path 为:
  // 堆元素个数为 0 时, path = 0 k = 0
  // 堆元素个数为 1 时, path = 00 k = 1
  // 堆元素个数为 2 时,  path = 01 k = 1
  // 堆元素个数为 3 时,  path = 000 k = 2
  // 堆元素个数为 4 时,  path = 010 k = 2
  // 堆元素个数为 5 时,  path = 001 k = 2
  // ... k 代表当前节点所在树的层级 path 中 0 和 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)
      child = &(*child)->right;
    else
      child = &(*child)->left;
    path >>= 1;
    k -= 1;
  }
  
  // path 从右往左 即 从上往下 遍历树
  // 遍历过后 parent 是 待插入位置 child 的父节点

  /* 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.
   */
 
  // 尾部新插入的节点 可能破坏了最小堆的 有序性: 父节点必须要比子节点都要小(即最小元素在根节点)
  // 所以需要从新节点 触发一次 上滤 维持最小堆的有序性
  // less_than 函数 是 用来比较节点的 libuv 就是 比较2个定时器容器的 超时时间的大小 
  while (newnode->parent != NULL && less_than(newnode, newnode->parent))
    heap_node_swap(heap, newnode->parent, newnode);
}

代码语言:javascript
复制
// 从堆中删除 node 
HEAP_EXPORT(void heap_remove(struct heap* heap,
                             struct heap_node* node,
                             heap_compare_fn less_than)) {
  struct heap_node* smallest;
  struct heap_node** max;
  struct heap_node* child;
  unsigned int path;
  unsigned int k;
  unsigned int n;

  if (heap->nelts == 0)
    return;

  /* Calculate the path from the min (the root) to the max, the left-most node
   * of the bottom row.
   */
  path = 0;
  for (k = 0, n = heap->nelts; n >= 2; k += 1, n /= 2)
    path = (path << 1) | (n & 1);

  /* Now traverse the heap using the path we calculated in the previous step. */
  max = &heap->min;
  while (k > 0) {
    if (path & 1)
      max = &(*max)->right;
    else
      max = &(*max)->left;
    path >>= 1;
    k -= 1;
  }
  
  // max 是当前树中最后一个位置的元素

  heap->nelts -= 1;

  /* Unlink the max node. */
  child = *max;
  *max = NULL;
  

  // 我们总是删除最后位置的元素
  // 如果待删除元素刚好是最后一个元素
  if (child == node) {
    /* We're removing either the max or the last node in the tree. */
    // 堆中刚好只有一个元素
    if (child == heap->min) {
      heap->min = NULL;
    }
    return;
  }

  /* Replace the to be deleted node with the max node. */
  // 把待删除节点的 各个指针赋值给 child 的各个指针
  child->left = node->left;
  child->right = node->right;
  child->parent = node->parent;

  // 然后重建 原 node 节点的父子指针 与 现在 child 的关系

  if (child->left != NULL) {
    child->left->parent = child;
  }

  if (child->right != NULL) {
    child->right->parent = child;
  }

  if (node->parent == NULL) {
    heap->min = child;
  } else if (node->parent->left == node) {
    node->parent->left = child;
  } else {
    node->parent->right = child;
  }
  
  // 替换完毕 现在的 child 替换到了 原有 node 所处的位置 

  /* Walk down the subtree and check at each node if the heap property holds.
   * It's a min heap so parent < child must be true.  If the parent is bigger,
   * swap it with the smallest child.
   */
   
  // 把原先最后的元素 替换到 node 位置后 可能会破坏原有的 堆有序性 需要进行一波 下滤 
  // 从替换节点位置 一直到树的最底层 恢复这部分子树的有序性
  for (;;) {
    smallest = child;
    if (child->left != NULL && less_than(child->left, smallest))
      smallest = child->left;
    if (child->right != NULL && less_than(child->right, smallest))
      smallest = child->right;
    if (smallest == child)
      // 已经有序了
      break;
    heap_node_swap(heap, child, smallest);
  }

  /* Walk up the subtree and check that each parent is less than the node
   * this is required, because `max` node is not guaranteed to be the
   * actual maximum in tree
   */
 
  // 最后从child做一次 上滤 保证一条影响到的路径中 所有节点符合 堆有序性
  while (child->parent != NULL && less_than(child, child->parent))
    heap_node_swap(heap, child->parent, child);
}
代码语言:javascript
复制
// 删除最小节点 删除节点的一种特殊情况
HEAP_EXPORT(void heap_dequeue(struct heap* heap, heap_compare_fn less_than)) {
  heap_remove(heap, heap->min, less_than);
}

总结:最小时间堆是libuv用来管理 定时器容器的,每个定时器容器以超时时间排序,插入到堆中,每次事件循环中查看是否有超时的定时任务。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档