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

二叉堆的基本结构

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小根堆中将最后一个元素在根节点空穴处进行下滤操作,如下图:
下滤1
下滤2
下滤3
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源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值