【高性能定时器】时间堆(最小堆)

最小堆及其应用:时间堆

    • 最小堆及其应用:时间堆
      • 一、 堆
        • 1. 概念
        • 2. 最小堆的实现
        • 3. 性质
        • 4. 代码
      • 二、时间堆
        • 1. 概念简述
        • 2. 实现细节
        • 3. 代码

一、 堆


1. 概念

堆是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于(或不小于)其左子节点和右子节点的值。

其中,两个叶子节点的大小没有顺序。

堆又分为两种,最大堆、最小堆。由上面的概念我们可以知道:
- 最大堆: 任一非叶子节点的值均大于其左子节点和右子节点的值。
- 最小堆: 任一非叶子节点的值均小于其左子节点和右子节点的值。

heap.png
(图为最小堆)

因此,我们可以得到:最大堆的根节点的值是最大的,最小堆的根节点的值是最小的。所以在需要最值问题的时候,我们可以采用堆这种数据结构来处理。


2. 最小堆的实现

由于堆是一种经过排序的完全二叉树,因此在构建的时候需要对新插入的节点进行一些操作以使其符合堆的性质。这种操作就是节点的上滤与下滤。以最小堆为例:

  • 上滤: 将当前节点与其父节点相比,如果当前节点的值比较小,就把当前节点与父节点交换,继续前面的比较,知道当前节点的值比父节点的值大为止。此时,便符合最小堆的定义。

  • 下滤: 将当前节点与其左、右子节点相比,如果当前节点的值比其中一个(或两个)子节点的值大,就把当前节点与两个子节点中较小的那个交换,继续前面的比较,知道当前节点的值比两个子节点的值都小为止。此时,便符合最小堆的定义。

下滤是将堆上面不符合条件的节点向下移动的过程,上滤则是将堆下面不符合条件的节点向上移动的过程。

a
交换后:
heap.png

3. 性质

  • 用数组模拟堆:对于第i个节点,其左子节点的位置是2i + 1, 右子节点的位置是2i + 2

  • 对于具有N个节点的完全二叉树,其叶子节点有( N + 1 ) / 2个,非叶子节点有N / 2个。

  • 由于在建立最小堆时,只要保证每个节点的值比其左右子节点都小即可,因此在建立最小堆时,只要从最下层开始,遍历前N / 2个非叶子节点( [ 0 ~ n/2-1 ] )进行下滤即可。(前提是已经有数组,不是每次向数组后面添加元素;叶子节点会由于其父节点的下滤而变得有序)

4. 代码

template< typename T >
void percolate_down( vector<T>& array,  int hole, int n ) {T tmp = array[hole];int child = 0;for( ; hole * 2 + 1 < n; hole = child ) {child = hole * 2 + 1;if( child < n-1 && array[child] > array[child+1] ) {  // 右子节点较小child++;  // 将节点切换到右子节点}if( tmp > array[child] ) {  // 子树的根节点值大于子节点值array[hole] = array[child];} else {  // tmp节点的值最小,符合break;}}array[hole] = tmp;  // 将最初的节点放到合适的位置
}/* 主函数用于建立最小堆的示例代码vector<int> t { 3, 6, 2, 1, 5, 4, 7 };
int n = t.size();for( int i = n/2 - 1; i >= 0; i-- ) {percolate_down( t, i, t.size() );
}*/

这里写图片描述

这里写图片描述


二、时间堆


1. 概念简述

由于定时器的触发是由于时间到了,因此只有时间最短的定时器会首先被触发,通过这个原理,我们可以采用最小堆,将按时间顺序排序,堆顶元素是时间最短的定时器,因此只要判断堆顶元素是否被触发即可。只有堆顶定时器的时间到了,才会到其他时间较晚的定时器的时间。


2. 实现细节

  • 堆顶节点的删除:将堆顶节点删除,就会留有一个空位置,因此可以将最后一个节点放到堆顶位置,再对堆顶节点进行下滤,就可以确保构成最小堆。

  • 使用数组来模拟堆的实现,相比于链表而言,不仅节省空间,而且更容易实现堆的插入、删除操作。如上面图片中的最小堆,可以用数组表示为:

  • 由于非叶子结点有N/2 - 1个,因此只要保证这些节点构成的子树具有堆性质,就能保证整棵树具有堆性质。(因为非叶子结点的下滤会将叶子节点也变的具有堆性质)

1326547

3. 代码

#ifndef _TIMEHEAP_H_
#define _TIMEHEAP_H_#include <iostream>
#include <netinet/in.h>
#include <time.h>const int BUFFER_SIZE = 64;class HeapTimer;// 用户数据,绑定socket和定时器
struct client_data {sockaddr_in address;int sock_fd;char buf[BUFFER_SIZE];HeapTimer* timer;
};// 定时器类
class HeapTimer {
public: time_t expire;  // 定时器生效的绝对时间client_data* user_data;
public: HeapTimer( int delay ) {expire = time( NULL ) + delay;}void ( *cb_func ) ( client_data* );  // 定时器的回调函数
};class TimeHeap {
private: HeapTimer** array;  // 堆数组int capacity;  // 堆数组容量int cur_size; // 堆数组当前包含元素个数
public: TimeHeap( int cap );  // 构造函数1,初始化大小为cap的空数组TimeHeap( HeapTimer** init_array, int size, int cap ); // 构造函数2,根据已有数组初始化堆~TimeHeap();
public:void percolate_down( int hole );  // 对堆结点进行下虑void add_timer( HeapTimer* timer );void del_timer( HeapTimer* timer );void pop_timer();void tick();void resize();
};TimeHeap::TimeHeap( int cap ) : capacity(cap), cur_size(0) {array = new HeapTimer*[ capacity ];if( !array ) {throw std::exception();}for( int i = 0; i < capacity; i++ ) {array[i] = nullptr;}
}TimeHeap::TimeHeap( HeapTimer** init_array, int size, int cap ) : cur_size(size), capacity(cap) {if( capacity < size ) {throw std::exception();}array = new HeapTimer*[ capacity ];if( !array ) {throw std::exception();}for( int i = 0; i < size; i++ ) {array[i] = init_array[i];}// 因为会比较当前节点与子节点,所以只从最下层遍历非叶子节点即可for( int i = size/2 - 1; i >= 0 ; i-- ) {percolate_down( i );}
}TimeHeap::~TimeHeap() {for( int i = 0; i < cur_size; i++ ) {if( !array[i] ) {delete array[i];} }delete[] array;
}// 对堆结点进行下滤,确保第hole个节点满足最小堆性质
void TimeHeap::percolate_down( int hole ) {HeapTimer* tmp = array[hole];int child = 0;for( ; hole * 2 + 1 < cur_size; hole = child ) {child = hole * 2 + 1;if( child < cur_size-1 && array[child]->expire > array[child+1]->expire ) {  // 右子节点较小child++;  // 将节点切换到右子节点}if( tmp->expire > array[child]->expire ) {  // 子树的根节点值大于子节点值array[hole] = array[child];} else {  // tmp节点的值最小,符合break;}}array[hole] = tmp;  // 将最初的节点放到合适的位置
}// 添加定时器,先放在数组末尾,在进行上滤使其满足最小堆
void TimeHeap::add_timer( HeapTimer* timer ) {if( !timer ) {return ;}if( cur_size >= capacity ) {resize();  // 空间不足,将堆空间扩大为原来的2倍}int hole = cur_size++;int parent = 0;// 由于新结点在最后,因此将其进行上滤,以符合最小堆for( ; hole > 0; hole = parent ) {parent = ( hole - 1 ) / 2;if( array[parent]->expire > timer->expire ) {array[hole] = array[parent];} else {break;}}array[hole] = timer;
}// 删除指定定时器
void TimeHeap::del_timer( HeapTimer* timer ) {if( !timer ) {return;}// 仅仅将回调函数置空,虽然节省删除的开销,但会造成数组膨胀timer->cb_func = nullptr;
}// 删除堆顶定时器
void TimeHeap::pop_timer() {if( !cur_size ) {return;}if( array[0] ) {delete array[0];array[0] = array[--cur_size];percolate_down( 0 );  // 对新的根节点进行下滤}
}// 从时间堆中寻找到时间的结点
void TimeHeap::tick() {HeapTimer* tmp = array[0];time_t cur = time( NULL );while( !cur_size ) {if( !tmp ) {break ;}if( tmp->expire > cur ) {  // 未到时间break;}if( array[0]->cb_func ) {array[0]->cb_func( array[0]->user_data );}pop_timer();tmp = array[0];}
}// 空间不足时,将空间扩大为原来的2倍
void TimeHeap::resize() {HeapTimer** tmp = new HeapTimer*[ capacity * 2 ];for( int i = 0; i < 2 * capacity; i++ ) {tmp[i] = nullptr;}if( !tmp ) {throw std::exception();}capacity *= 2;for( int i = 0; i < cur_size; i++ ) {tmp[i] = array[i];}delete[] array;array = tmp;
}#endif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/381865.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uva 11584——Partitioning by Palindromes

题意&#xff1a;给定一个字符串&#xff0c;把该字符串划分为最少的回文串。 思路&#xff1a;dp&#xff0c;到达i点的回文串长度都存起来&#xff0c;那么dp[i]min(以i为结尾的最短的回文串长度&#xff09;。 code&#xff1a; #include <bits/stdc.h> using namespa…

【操作系统】生产者消费者问题

生产者消费者模型 文章目录生产者消费者模型 [toc]一、 生产者消费者问题二、 问题分析三、 伪代码实现四、代码实现&#xff08;C&#xff09;五、 互斥锁与条件变量的使用比较一、 生产者消费者问题 生产者消费者问题&#xff08;英语&#xff1a;Producer-consumer problem&…

2006年2月8日 再见,Borland

昨天早上从David I的Blog上看到了Borland出售IDE产品线转而全面发展ALM的消息,既感到惋惜又似乎感到新的希望 之所以惋惜是因为Delphi是我第一个喜欢的语言&#xff0c;也是Delphi第一次让我对编程产生兴趣&#xff0c;还记得我的第一个小软件就是Delphi5.0做的&#xff0c;而如…

【计算机网络】三次握手与四次挥手

三次握手与四次挥手 通过TCP/IP协议的学习&#xff0c;我们可以知道TCP协议是一种面向连接的、可靠的传输协议。其中&#xff0c;为了保证客户端与服务器连接的有效性&#xff0c;就有了本篇文章所要介绍的“三次挥手”&#xff1b;而“四次挥手”则是为了保证连接的正确断开。…

uva 10003——Cutting Sticks

题意&#xff1a;给定一长为L的木棍和n个切割点&#xff0c;每次切割的费用为切割的长度&#xff0c;求最小的费用。 思路&#xff1a;dp,子问题是区间&#xff08;i&#xff0c;j&#xff09;的最小费用&#xff0c;临界是&#xff08;i,j)只有一个切割点。dp[i,j]min(dp[i,k]…

小叔叔又飞走了

小叔叔在飞走之前&#xff0c;昨天晚上&#xff0c;给我们看了&#xff0c;他回老家拍下的&#xff0c;家里每个人的视频&#xff0c;&#xff08;前面这半句话说得好累&#xff0c;不过应该不是病句&#xff09;让我们看了动情又亲切&#xff0c;至少我是这么觉得。一个大家庭…

系统调用中断(EINTR)与SIGCHLD信号的处理

一、被中断的系统调用(EINTR)的理解1. 慢系统调用是&#xff1f;2. 慢系统调用的类别3. EINTR产生的原因5. 一般处理方法 二、SIGCHLD信号的处理1. SIGCHLD信号的产生2. SIGCHLD信号的处理3. 不处理SIGCHLD的后果 三、示例代码 一、被中断的系统调用(EINTR)的理解 1. 慢系…

SMO写的查看数据库信息的代码

要分析一个比较大的数据库&#xff0c;里面的表太多了&#xff0c;虽然是中文命名&#xff0c;但在2005的Management Studio中查看还是比较麻烦&#xff0c;比如&#xff0c;我想查看具有相同字段名称的表的情况就不好办。于是用SMO写了这个东东。代码比较乱&#xff0c;没有进…

uva 1626——Brackets sequence

题意&#xff1a;定义满足 1.空序列 2.&#xff08;&#xff09;&#xff08;X&#xff09;及括号和其括起来的合法序列 3.【】要求和&#xff08;&#xff09;相同 都是合法的串。 然后给定一段序列&#xff0c;求添加最小的&#xff08;&#xff09;或【】使得序列合法。…

定时器与超时的设置

一、相关时间函数1. gettimeofday()2. time()3. clock() 二、间隔定时器1. setitimerval()2. getitimerval()3. 实时定时器的使用 三、为阻塞操作设置超时1. alarm()2. 给read()设置读超时 一、相关时间函数 1. gettimeofday() 获取日历时间。 #include <sys/time.…

世界 Web 2.0 网站评奖揭晓

Web 2.0真的成了我们在互联网不可或缺的一部分&#xff0c;成了互联网的未来趋势.(你现在用了吗?)国外的 SEOmoz 网站最近举行了一个 Over 300 web 2.0 sites reviewed and ranked 活动列出了300多家 Web 2.0 网站, 分为38个分类, 评出了每个类别下的前三名, 并采访了 20位获奖…

win10宽带连接断网自动重连

文章目录1. 断开网络连接&#xff0c;重命名网络连接2. bat代码&#xff1a;检测到断线自动重连3. 设置开机自动执行3.1 方式一&#xff1a;任务计划程序3.2 方式二&#xff1a;用vbs代码开机运行bat1. 断开网络连接&#xff0c;重命名网络连接 中文名字可能出现远程访问错误 …

C#正在被人用来做什么?--在CSDN上引发小讨论的帖子

C#正在被人用来做什么&#xff1f;--在CSDN上引发小讨论的帖子 主  题&#xff1a; C#正在被人用来做什么&#xff1f; 作  者&#xff1a; manio (马牛) 等  级&#xff1a; 信 誉 值&#xff1a; 100 所属社区&#xff1a; .NET技术 C# 问题点数&#xff1a…

uva 1025——A Spy in the Metro

题意&#xff1a;有一个线性的车站&#xff08;1-n&#xff09;&#xff0c;两个方向的车&#xff0c;给出列车的出发时刻和到下一站的时间&#xff0c;要求在到达n前换乘的等待时间最短。 思路&#xff1a;dp&#xff0c;每次有3种决策&#xff0c;要么等一分钟&#xff0c;要…

ubuntu20环境下使用DevStack安装Openstack-Wallaby(单节点、多节点)

文章目录一、单节点部署1. 环境准备1.1 镜像源1.2 pip源1.3 安装依赖包2. OpenStack安装 - wallaby2.1 添加stack用户2.2 设置代理2.3 下载devstack&#xff0c;使用-b指定版本2.4 进入devstack目录&#xff0c;编辑配置文件2.5 开始安装2.6 安装完成二、多节点部署1. 环境准备…

EMF的一些总结(2)——关于EMF的序列化

关于EMF的序列化对于EMF的序列化有几个比较重要的接口&#xff1a;Resource,ResourceSet,Resource.Factory,URIConverter。这些接口的主要作用就是保存模型到持久化存储介质&#xff0c;或者从持久化存储介质加载模型。1&#xff0e;关于URI&#xff08;Uniform Resource Ident…

uva 12563——Jin Ge Jin Qu hao

题意&#xff1a;给出n首歌及每首歌的播放时间&#xff0c;然后在t秒内唱这些歌&#xff0c;最后的剩余时间要大于0&#xff1b; 思路&#xff1a;01背包问题&#xff0c;对于没次选择&#xff0c;只有取或不取两种状态&#xff0c;只需在这两种状态中找到最优的策略即可。 cod…

可以在xml中靠增加属性来实现分组

可以在一篇xml文档中&#xff0c;靠增加一个行的属性&#xff0c;来进行排序。有这方面需求的folks&#xff0c;请参考如下代码&#xff1a; private void UniteHtmlCell(ref XmlDocument xDocument) { int rowsCount xDocument.DocumentElement.ChildNodes…

uva 1220——Party at Hali-Bula

题意&#xff1a;公司里n个人&#xff0c;要举办一场party&#xff0c;但是到场的人不能碰到他的直隶上司&#xff0c;问最多能到场多少人。 思路&#xff1a;树形dp&#xff0c;就是求在树上的最大独立集合&#xff0c;加唯一性特判。dp(u&#xff0c;0)表示不选u点的条件最大…

Vi编辑器的基本使用方法

Vi编辑器的基本使用方法- Tag&#xff1a; linux Vi 使用方法 来自&#xff1a;Linux公社 vi编辑器是所有Unix及Linux系统下标准的编辑器&#xff0c;它的强大不逊色于任何最新的文本编辑器&#xff0c;这里只是简单地介…