libhv学习笔记5:heap.h分析

上一篇分析了libhv里面用到的链表的实现,今天我们看一下定时器超时事件中用到的堆得实现。

堆定义

1.堆中的某个节点总是不大于或不小于其父亲节点
2.堆总是一颗完全二叉树

那么,最大堆就是父节点比每一个子节点值都要大。最小堆就是父节点比每一个子节点值要小。
例子(最小堆)
libhv学习笔记5:heap.h分析_第1张图片

完全二叉树的定义

对于一个树高为h的二叉树,如果其第0层至第h-1层的节点都满。如果最下面一层节点不满,则所有的节点在左边的连续排列,空位都在右边。这样的二叉树就是一棵完全二叉树

源码分析

结构体的定义

struct heap_node {
     
    struct heap_node* parent; //父节点
    struct heap_node* left;	  //左孩子
    struct heap_node* right;  //右孩子
};

typedef int (*heap_compare_fn)(const struct heap_node* lhs, const struct heap_node* rhs);
struct heap {
     
    struct heap_node* root;  // 根节点
    int nelts;				 // 节点的个数
    // if compare is less_than, root is min of heap
    // if compare is larger_than, root is max of heap
    heap_compare_fn compare;
};

堆初始化

static inline void heap_init(struct heap* heap, heap_compare_fn fn) {
     
    heap->root = NULL;  
    heap->nelts = 0;
    heap->compare = fn;  //确定最大堆还是最小堆
}

节点插入
在堆中,节点的插入主要分为两个步骤:
1.将节点插入到堆得尾部。
2.调整节点的位置,使其满足堆得特性。
例如:
libhv学习笔记5:heap.h分析_第2张图片

插入节点2
libhv学习笔记5:heap.h分析_第3张图片

调整位置
libhv学习笔记5:heap.h分析_第4张图片
libhv学习笔记5:heap.h分析_第5张图片
这就是节点插入的过程,下面看一下libhv怎么处理的

static inline void heap_insert(struct heap* heap, struct heap_node* node) {
     
    // get last => insert node => sift up
    // 0: left, 1: right
    int path = 0;
    int n,d;
    ++heap->nelts;
    // traverse from bottom to up, get path of last node
    for (d = 0, n = heap->nelts; n >= 2; ++d, n>>=1) {
     
        path = (path << 1) | (n & 1);
    }

    // get last->parent by path
    struct heap_node* parent = heap->root;
    while(d > 1) {
     
        parent = (path & 1) ? parent->right : parent->left;
        --d;
        path >>= 1;
    }

    // insert node
    node->parent = parent;
    if (parent == NULL) heap->root = node;
    else if (path & 1) parent->right = node;
    else parent->left = node;

    // sift up
    if (heap->compare) {
     
        while (node->parent && heap->compare(node, node->parent)) {
     
            heap_swap(heap, node->parent, node);
        }
    }
}

过程就是

// get last => insert node => sift up

先看怎么get last,也就是获取节点插入的位置

   // traverse from bottom to up, get path of last node
    for (d = 0, n = heap->nelts; n >= 2; ++d, n>>=1) {
     
        path = (path << 1) | (n & 1);
    }

这里是获取插入节点的路径,从根出发,沿怎样的路径找到插入的位置。
辅以图讲解
libhv学习笔记5:heap.h分析_第6张图片
现在我们要插入节点7。
那么 n= heap->nelts = 7. 各变量的变化过程是:

n:7 ▶ 3 ▶ 1
d:0 ▶ 1 ▶ 2
path:0001 ▶0011

此时得到的路径为11B,0为左孩子,1为右孩子,也就是说,从根出发,根的右孩子,右孩子的右孩子

看一下代码

  // get last->parent by path
    struct heap_node* parent = heap->root;
    while(d > 1) {
     
        parent = (path & 1) ? parent->right : parent->left;
        --d;
        path >>= 1;
    }

那么parent指向的就是3节点
插入节点

 // insert node
    node->parent = parent;    //设置父节点
    if (parent == NULL) heap->root = node;  //如果父节点为NULL,说明新加入的节点是根结点
    else if (path & 1) parent->right = node; //新加入的节点是其父节点的右结点
    else parent->left = node;   //新加入的节点是其父节点的左节点

接下来就是调整位置,使其满足堆得定义

if (heap->compare) {
     
        while (node->parent && heap->compare(node, node->parent)) {
     
            heap_swap(heap, node->parent, node); //交换节点与父节点
        }
    }
static inline void heap_swap(struct heap* heap, struct heap_node* parent, struct heap_node* child) {
     
    assert(child->parent == parent && (parent->left == child || parent->right == child));
    //parent的父节点
    struct heap_node* pparent = parent->parent;
    //child的左孩子
    struct heap_node* lchild = child->left;
    //child的右孩子
    struct heap_node* rchild = child->right;
    struct heap_node* sibling = NULL;
	
	//[parent的父节点更换孩子为child]
	//如果parent的父节点为空,说明parent是根节点
    if (pparent == NULL) heap->root = child;
    //如果parent是父节点的左孩子,则把左孩子指向child
    else if (pparent->left == parent) pparent->left = child;
    //如果parent是父节点的右孩子,则把右孩子指向child
    else if (pparent->right == parent) pparent->right = child;
    
    //[child的孩子更换父节点]
    //如果child有左孩子,则把左孩子指向parent
    if (lchild) lchild->parent = parent;
     //如果child有左孩子,则把左孩子指向parent
    if (rchild) rchild->parent = parent;

	//child更换parent
    child->parent  = pparent;
   //如果child是parent的左孩子,则把parent的变为child的左孩子
    if (parent->left == child) {
     
        sibling = parent->right;
        child->left = parent;
        child->right = sibling;
    }
    //如果child是parent的右孩子,则把parent的变为child的右孩子
    else {
     
        sibling = parent->left;
        child->left = sibling;
        child->right = parent;
    }
    //将parent另一个孩子的父节点改为child
    if (sibling) sibling->parent = child;

    parent->parent = child;
    parent->left   = lchild;
    parent->right  = rchild;
}

节点删除

static inline void heap_remove(struct heap* heap, struct heap_node* node) {
     
    if (heap->nelts == 0)   return;
    // get last => replace node with last => sift down and sift up
    // 0: left, 1: right
    int path = 0;
    int n,d;
    // traverse from bottom to up, get path of last node
    //获取最后一个节点的路径
    for (d = 0, n = heap->nelts; n >= 2; ++d, n>>=1) {
     
        path = (path << 1) | (n & 1);
    }
    --heap->nelts;

    // get last->parent by path
    //获取最后一个节点的父节点
    struct heap_node* parent = heap->root;
    while(d > 1) {
     
        parent = (path & 1) ? parent->right : parent->left;
        --d;
        path >>= 1;
    }

    // replace node with last
    struct heap_node* last = NULL;
    if (parent == NULL) {
     
        return;
    }
    //获取最后一个节点,并在堆中将该位置赋值为NULL,相当于把node删除了
    else if (path & 1) {
     
        last = parent->right;
        parent->right = NULL;
    }
    else {
     
        last = parent->left;
        parent->left = NULL;
    }
    //说明只有一个根节点
    if (last == NULL) {
     
        if (heap->root == node) {
     
            heap->root = NULL;
        }
        return;
    }
    //交换node和last
    heap_replace(heap, node, last);
    node->parent = node->left = node->right = NULL;

    if (!heap->compare) return;
    struct heap_node* v = last;
    struct heap_node* est = NULL;
    // sift down
    //last有可能比过去node的孩子结点大,那么需要将last向下移动
    while (1) {
     
        est = v;
        if (v->left) est = heap->compare(est, v->left) ? est : v->left;
        if (v->right) est = heap->compare(est, v->right) ? est : v->right;
        if (est == v) break;
        heap_swap(heap, v, est);
    }
    // sift up
    //last有可能比过去node的父结点小,那么需要将last向上移动
    while (v->parent && heap->compare(v, v->parent)) {
     
        heap_swap(heap, v->parent, v);
    }
}

删除的大体过程就是,把删除的节点和最后一个节点交换,然后再调整交换后的节点的位置,使其满足堆得定义。

// replace s with r
static inline void heap_replace(struct heap* heap, struct heap_node* s, struct heap_node* r) {
     
    // s->parent->child, s->left->parent, s->right->parent
    //s为根节点
    if (s->parent == NULL) heap->root = r;
    //如果s是父节点的左孩子,则r变为s左孩子
    else if (s->parent->left == s) s->parent->left = r;
    //如果s是父节点的右孩子,则r变为s右孩子
    else if (s->parent->right == s) s->parent->right = r;

    //把s的孩子的父节点变为r
    if (s->left) s->left->parent = r;
    if (s->right) s->right->parent = r;
    r的父节点,孩子节点变为s的父节点和孩子节点
    if (r) {
     
        //*r = *s;
        r->parent = s->parent;
        r->left = s->left;
        r->right = s->right;
    }
}

删除根节点

static inline void heap_dequeue(struct heap* heap) {
     
    heap_remove(heap, heap->root);
}

再看一下堆在定时器结构体里的应用

#define HTIMER_FIELDS                   \
    HEVENT_FIELDS                       \
    uint32_t    repeat;                 \
    uint64_t    next_timeout;           \
    struct heap_node node;

struct htimer_s {
     
    HTIMER_FIELDS
};
#define TIMER_ENTRY(p)          container_of(p, htimer_t, node)

和链表使用的思想是一样的。

以上就是libhv里堆的实现。

你可能感兴趣的:(C/C++网络,libhv)