时间堆定时器实现

文中前半部分图片大部分来自:https://www.cnblogs.com/chengxiao/p/6129630.html
堆的基本概念:
  堆是具有以下性质的完全二叉树:每个父结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个父结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
大小堆如下图:
在这里插入图片描述
我们将这种逻辑结构映射到数组中,如下图:
在这里插入图片描述
我们通过两组简单的公式描述一下上述两种堆的特性:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序基本原理和步骤:

1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了(一般升序采用大顶堆,降序采用小顶堆),同时堆排序一种平均时间复杂度最坏、最好均为O(nlogn),它也是不稳定排序。

步骤一
构造初始堆将给定无序序列构造成一个大顶堆(升序采用大顶堆,降序采用小顶堆)。
在这里插入图片描述
2.从最后一个非叶子结点开始(arr.length/2-1=5/2-1=1,也就是6结点),从左至右,从下至上进行调整。
在这里插入图片描述
4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
在这里插入图片描述
在这里插入图片描述
交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6,到这里,我们就将一个无需序列构造成了一个大顶堆。

步骤二
将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

a.将堆顶元素9和末尾元素4进行交换
在这里插入图片描述
b.重新调整结构,使其继续满足堆定义
在这里插入图片描述
c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
在这里插入图片描述
后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

在这里插入图片描述
再简单总结下堆排序的基本思路:
  a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
  c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

大小顶堆代码实现如下:

void heap_adjust_big(int ch[], int parent, int lengh)
{
    
    
	int temp = ch[parent];
	

	for (int child = 2*parent+1; child<lengh; child = child*2+1)
	{
    
    
		if (child+1<lengh && ch[child]<ch[child+1])
		{
    
    
			child++;
		}

		if (ch[child]>temp)
		{
    
    
			ch[parent] = ch[child];
			parent = child;
		}
		else
		{
    
    
			break;
		}
	}
	ch[parent] = temp;
}

void heap_sort_big(int ch[], int lengh)
{
    
    
	for (int i = lengh/2-1; i>=0; i--)
	{
    
    
		heap_adjust_big(ch, i, lengh);
	}

	for (int j = lengh-1; j>0; j--)
	{
    
    
		int temp = ch[j];
		ch[j] = ch[0];
		ch[0] = temp;

		heap_adjust_big(ch, 0, j);
	}
}

void heap_adjust_small(int ch[], int parent, int lengh)
{
    
    
	int temp = ch[parent];


	for (int child = 2*parent+1; child<lengh; child = child*2+1)
	{
    
    
		if (child+1<lengh && ch[child]>ch[child+1])
		{
    
    
			child++;
		}

		if (ch[child]<temp)
		{
    
    
			ch[parent] = ch[child];
			parent = child;
		}
		else
		{
    
    
			break;
		}
	}
	ch[parent] = temp;
}

void heap_sort_small(int ch[], int lengh)
{
    
    
	for (int i = lengh/2-1; i>=0; i--)
	{
    
    
		heap_adjust_small(ch, i, lengh);
	}

	for (int j = lengh-1; j>0; j--)
	{
    
    
		int temp = ch[j];
		ch[j] = ch[0];
		ch[0] = temp;

		heap_adjust_small(ch, 0, j);
	}
}

经过上面的基础只是铺垫,下面我们来看一下基于最小堆原理实现的定时器
时间堆的概念:
由于定时器的触发是由于时间到了,因此只有时间最短的定时器会首先被触发,通过这个原理,我们可以采用最小堆,将按时间顺序排序,堆顶元素是时间最短的定时器,因此只要判断堆顶元素是否被触发即可。只有堆顶定时器的时间到了,才会到其他时间较晚的定时器的时间。
时间堆解决的问题:
首先我们应该都知道定时器的概念,实现起来也很简单,开个线程循环调用就行了,但那只是少量定时器的场景适用;如果业务中有几万个定时业务,那难道我们要开几万个线程吗?这几乎是不可能会有人这样干的,耗性能系统也撑不住,这时候就需要我们设计出这样一种数据结构(有序升序、插入删除速度极快),然后我们将过期时间(timeout)映射到这个有序的序列中,然后我们用一个线程轮询检测这个队列最小头部(时间最小也就是过期最快的元素),这时候我们就能以极小的代价和极高的效率管理这几万个定时任务,恰好最小堆就是这样一种数据结构;

最小堆定时器实现分析
实现最小堆定时器步骤:
1讲时间序列按照小顶堆排序,这样序列头就是最小的时间元素;
2每次检测队列头是否过期即可,不必全量序列检测;
3删除队列头后只需要将堆尾移到被删除的堆顶,然后重上到下调整为小顶堆即可;
4插入定时器只需要讲其先插入到堆尾部,然后将其循环和父节点比较上浮,最后构建成最小堆即可;
5直接删除任意定时器(定时业务异常需要删除),其实这个可以直接查找到这个节点,并且做个标记就行了,没必要真正删除,然后再去调整堆,等到这个标记的定时器过期在删除就好(等到这个节点上浮到堆顶);

话不多说上代码

扫描二维码关注公众号,回复: 12595273 查看本文章
#include <iostream>
#include <netinet/in.h>
#include <time.h>

class HeapTimer {
    
    
public: 
	time_t time_out;
	void* p_data;
	HeapTimer( int delay ) {
    
    
		time_out = time( NULL ) + delay;
	}
	void ( *callback ) ( void* );
};

class TimerMgr{
    
    
private: 
	HeapTimer** array;
	int capacity;  // 容量
	int used_size; // 当前元素个数
public: 
	TimerMgr( int cap );
	TimerMgr( HeapTimer** init_array, int size, int cap );
	~TimerMgr();
public:
	void heap_adjust_small( int index );
	void add_timer( HeapTimer* timer );
	void del_timer( HeapTimer* timer );
	void pop_timer();
	void tick();

	void resize();
};

TimerMgr::TimerMgr( int cap ) : capacity(cap), used_size(0) {
    
    
	array = new HeapTimer*[ capacity ];

	for( int i = 0; i < capacity; i++ ) {
    
    
		array[i] = nullptr;
	}
}

TimerMgr::TimerMgr( HeapTimer** init_array, int size, int cap ) : used_size(size), capacity(cap) {
    
    
	if( capacity < size ) {
    
    
		return;
	}

	array = new HeapTimer*[ capacity ];

	for( int i = 0; i < size; i++ ) {
    
    
		array[i] = init_array[i];
	}

	//构建小顶堆
	for( int i = size/2 - 1; i >= 0 ; i-- ) {
    
    
		heap_adjust_small( i );
	}
}

TimerMgr::~TimerMgr() {
    
    
	for( int i = 0; i < used_size; i++ ) {
    
    
		if( !array[i] ) {
    
    
			delete array[i];
		} 
	}
	delete[] array;
}

void TimerMgr::heap_adjust_small( int parent ) {
    
    
	HeapTimer* tmp = array[parent];
	
	for( int child = parent*2+1; child < used_size; child = child*2+1) {
    
    

		if( child+1<used_size && array[child]->time_out > array[child+1]->time_out ){
    
      
			child++; 
		}
		if( tmp->time_out > array[child]->time_out ){
    
      

			array[parent] = array[child];
			parent = child;
		} else {
    
      
			break;
		}
	}
	array[parent] = tmp;  
}

//先放在数组末尾,在进行上滤使其满足最小堆
void TimerMgr::add_timer( HeapTimer* timer ) {
    
    
	if( !timer ) {
    
    
		return ;
	}

	if( used_size >= capacity ) {
    
    
		resize();
	}

	int hole = used_size++;

	// 由于新结点在最后,因此将其进行上滤,以符合最小堆
	for( int parent = (hole-1)/2; hole > 0;  parent = (hole-1)/2){
    
    

		if( array[parent]->time_out > timer->time_out ) {
    
    

			array[hole] = array[parent];
			hole = parent;
		} else {
    
    

			break;
		}
	}
	array[hole] = timer;
}

void TimerMgr::del_timer( HeapTimer* timer ) {
    
    
	if( !timer ) {
    
    
		return;
	}
	//tick到了自然删掉(节省删除的开销,数组膨胀很小)
	timer->callback = nullptr;
}

void TimerMgr::pop_timer() {
    
    
	if( !used_size ) {
    
    
		return;
	}
	if( array[0] ) {
    
    
		delete array[0];
		array[0] = array[--used_size];
		heap_adjust_small( 0 );
	}
}

void TimerMgr::tick() {
    
    
	HeapTimer* tmp = array[0];
	time_t cur = time( NULL );
	while( !used_size ) {
    
    
		if( !tmp ) {
    
    
			break ;
		}
		if( tmp->time_out > cur ) {
    
    
			break;
		}
		if( array[0]->callback ) {
    
    
			array[0]->callback( array[0]->p_data );
		}
		pop_timer();
		tmp = array[0];
	}
}

// 扩容 N=2N
void TimerMgr::resize() {
    
    
	HeapTimer** tmp = new HeapTimer*[ capacity * 2 ];
	for( int i = 0; i < 2 * capacity; i++ ) {
    
    
		tmp[i] = nullptr;
	}

	capacity *= 2;
	for( int i = 0; i < used_size; i++ ) {
    
    
		tmp[i] = array[i];
	}
	delete[] array;
	array = tmp;
}

#endif

猜你喜欢

转载自blog.csdn.net/wangrenhaioylj/article/details/108565134