libevent使用二叉堆来管理timer事件,其key值为超时时间,二叉堆是一颗被完全填满的二叉树,最底层可能有例外,且底层元素都是从左到右填入的。
考虑到完全二叉树的规律性,其很容易使用数组来表示,libevent也是使用数组的方式来表示的,小根堆的结构体如下:
typedef struct min_heap
{
struct event** p; // 指向二叉堆根节点,堆成员为指向事件event的指针
unsigned n/* 堆中有效元素个数 */, a/* 堆总大小,单位是个而不是字节 */;
} min_heap_t;
数组表示法如下:
堆序性:使操作被快速执行的性质,在libevent中,小根堆的堆序性为快速找出最小元,故最小元应该在根上,且任意子树都应遵循该性质,即任意节点都应小于其所有后裔。
libevent中采用上滤策略进行插入,即新元素在堆中上滤直到找出正确的位置,如下图:
libevent代码实现如下:
// 将e插入到堆s中,从空穴hole_index开始上滤,和父节点比,如果比父节点大停止
void min_heap_shift_up_(min_heap_t* s, unsigned hole_index/* 空穴位置,从此位置开始上滤 */, struct event* e)
{
unsigned parent = (hole_index - 1) / 2; // 父节点
while (hole_index && min_heap_elem_greater(s->p[parent], e))
{ // 如果不是在根节点插入(hole_index!=0)且父节点大于待插入的元素继续循环
(s->p[hole_index] = s->p[parent])->ev_timeout_pos.min_heap_idx = hole_index;
hole_index = parent; // 上移空穴
parent = (hole_index - 1) / 2; // 计算新的父节点
}
(s->p[hole_index] = e)->ev_timeout_pos.min_heap_idx = hole_index; // 将e插入二叉堆中,并更新位置变量
}
libevent中采用下滤策略进行删除,由于根节点是最小元已被弹出,故在此构建空穴,libevent小根堆中将最后一个元素在根节点空穴处进行下滤操作,如下图:
libevent代码实现:
// 将e插到位置hole_index,从位置hole_index开始下滤,和最小的子节点比,如果比最小的小停止
void min_heap_shift_down_(min_heap_t* s, unsigned hole_index/* 空穴位置,从此位置开始下滤 */, struct event* e)
{
unsigned min_child = 2 * (hole_index + 1); // 计算右儿子节点下标
while (min_child <= s->n)
{
min_child -= min_child == s->n || min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]); // 计算最小儿子节点下标,注意运算符优先级
if (!(min_heap_elem_greater(e, s->p[min_child]))) // 如果待插入元素比最小的儿子都小那么就跳出循环
break;
(s->p[hole_index] = s->p[min_child])->ev_timeout_pos.min_heap_idx = hole_index;
hole_index = min_child; // 下移空穴
min_child = 2 * (hole_index + 1); // 计算新的右儿子节点下标
}
(s->p[hole_index] = e)->ev_timeout_pos.min_heap_idx = hole_index; // 将e插入二叉堆中,并更新位置变量
}
返回堆顶元素:
struct event* min_heap_top_(min_heap_t* s) { return s->n ? *s->p : 0; }
事件e是否在小根堆的顶部:
int min_heap_elt_is_top_(const struct event *e)
{
return e->ev_timeout_pos.min_heap_idx == 0;
}
分配堆空间:
int min_heap_reserve_(min_heap_t* s, unsigned n)
{
if (s->a < n)
{
struct event** p;
unsigned a = s->a ? s->a * 2 : 8;
if (a < n)
a = n;
if (!(p = (struct event**)mm_realloc(s->p, a * sizeof *p)))
return -1;
s->p = p;
s->a = a;
}
return 0;
}
插入:
int min_heap_push_(min_heap_t* s, struct event* e)
{
if (min_heap_reserve_(s, s->n + 1)) // 插入前先检查堆空间够不够,不够的话分配新的空间
return -1;
min_heap_shift_up_(s, s->n++, e);// 上移插入
return 0;
}
弹出堆顶元素:
struct event* min_heap_pop_(min_heap_t* s)
{
if (s->n) // 树中有元素
{
struct event* e = *s->p; // 堆中第一个元素,也即最小元素
min_heap_shift_down_(s, 0u, s->p[--s->n]); // 最后一个元素插往位置0,下滤操作
e->ev_timeout_pos.min_heap_idx = -1;
return e;
}
return 0;
}
无条件上滤操作:
void min_heap_shift_up_unconditional_(min_heap_t* s, unsigned hole_index, struct event* e)
{
unsigned parent = (hole_index - 1) / 2;
do
{
(s->p[hole_index] = s->p[parent])->ev_timeout_pos.min_heap_idx = hole_index;
hole_index = parent;
parent = (hole_index - 1) / 2; // 计算新的父节点
} while (hole_index && min_heap_elem_greater(s->p[parent], e));
(s->p[hole_index] = e)->ev_timeout_pos.min_heap_idx = hole_index;
}
删除操作:
int min_heap_erase_(min_heap_t* s, struct event* e)
{
if (-1 != e->ev_timeout_pos.min_heap_idx)
{
struct event *last = s->p[--s->n]; // 将最后一个元素以上移法或者下移法移动到要删除的e的位置
unsigned parent = (e->ev_timeout_pos.min_heap_idx - 1) / 2;
/* we replace e with the last element in the heap. We might need to
shift it upward if it is less than its parent, or downward if it is
greater than one or both its children. Since the children are known
to be less than the parent, it can't need to shift both up and
down. */
if (e->ev_timeout_pos.min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], last))
min_heap_shift_up_unconditional_(s, e->ev_timeout_pos.min_heap_idx, last);
else
min_heap_shift_down_(s, e->ev_timeout_pos.min_heap_idx, last);
e->ev_timeout_pos.min_heap_idx = -1;
return 0;
}
return -1;
}
调整指定节点位置:
// 调整e在堆中位置
int min_heap_adjust_(min_heap_t *s, struct event *e)
{
if (-1 == e->ev_timeout_pos.min_heap_idx) { // 如果e不在堆中就插入
return min_heap_push_(s, e);
} else {
unsigned parent = (e->ev_timeout_pos.min_heap_idx - 1) / 2;
/* The position of e has changed; we shift it up or down
* as needed. We can't need to do both. */
if (e->ev_timeout_pos.min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], e))
min_heap_shift_up_unconditional_(s, e->ev_timeout_pos.min_heap_idx, e);
else
min_heap_shift_down_(s, e->ev_timeout_pos.min_heap_idx, e);
return 0;
}
}
以上就是libevent小根堆的实现方式。
数据结构与算法分析(第二版)第六章
上述上滤下滤图片也是按照上书画的
libevent-2.1.8-stable源码