libevent学习笔记四——timer小根堆

二叉堆的基本结构

libevent使用二叉堆来管理timer事件,其key值为超时时间,二叉堆是一颗被完全填满的二叉树,最底层可能有例外,且底层元素都是从左到右填入的。
libevent学习笔记四——timer小根堆_第1张图片
考虑到完全二叉树的规律性,其很容易使用数组来表示,libevent也是使用数组的方式来表示的,小根堆的结构体如下:

typedef struct min_heap
{
	struct event** p;	// 指向二叉堆根节点,堆成员为指向事件event的指针
	unsigned n/* 堆中有效元素个数 */, a/* 堆总大小,单位是个而不是字节 */;
} min_heap_t;

数组表示法如下:
libevent学习笔记四——timer小根堆_第2张图片
堆序性:使操作被快速执行的性质,在libevent中,小根堆的堆序性为快速找出最小元,故最小元应该在根上,且任意子树都应遵循该性质,即任意节点都应小于其所有后裔。

二叉堆的基本操作

插入操作

libevent中采用上滤策略进行插入,即新元素在堆中上滤直到找出正确的位置,如下图:
libevent学习笔记四——timer小根堆_第3张图片
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学习笔记四——timer小根堆_第4张图片
libevent学习笔记四——timer小根堆_第5张图片
libevent学习笔记四——timer小根堆_第6张图片
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插入二叉堆中,并更新位置变量
}

libevent相应API简述

返回堆顶元素:

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源码

你可能感兴趣的:(libevent学习笔记)