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

最小堆及其应用:时间堆

    • 最小堆及其应用:时间堆
      • 一、 堆
        • 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,一经查实,立即删除!

相关文章

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

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

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

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

小叔叔又飞走了

小叔叔在飞走之前&#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;没有进…

win10宽带连接断网自动重连

文章目录1. 断开网络连接&#xff0c;重命名网络连接2. bat代码&#xff1a;检测到断线自动重连3. 设置开机自动执行3.1 方式一&#xff1a;任务计划程序3.2 方式二&#xff1a;用vbs代码开机运行bat1. 断开网络连接&#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…

单链表实例之学生系统

单链表实例之学生系统 #include<stdio.h> #include<stdlib.h>#define NAME_LEN 20typedef struct node {int number;char *name;struct node *next; } node_t;node_t *g_head; int add_stu() {node_t *new, *tmp;tmp g_head;char *name;new (node_t *)malloc(si…

公司培训文档-JavaScript[对象.属性]集锦

SCRIPT 标记 用于包含JavaScript代码. 属性 LANGUAGE 定义脚本语言 SRC 定义一个URL用以指定以.JS结尾的文件 windows对象 每个HTML文档的顶层对象. 属性 frames[] 子桢数组.每个子桢数组按源文档中定义的顺序存放. feames.length 子桢个数. self 当前窗口. parent …

查询分析器下如何备份数据库

查询分析器下如何备份数据库backupdatabasemsdb todiskd:\msdb.bakOK搞定转载于:https://www.cnblogs.com/CnKker/archive/2006/09/26/515318.html

Inside Dynamics Axapta源代码赏析(四)

第八章:Developing Applications Using Business Connector 这一章的代码主要演示如何通过Business Connector与Axapta交互在Dynamics Axapta的客户端安装目录中找到Microsoft.Dynamics.BusinessConnectorNet.dll这个文件,添加到VS.NET的工程中.1.HelloWorldBC.csclassHelloWor…

信号之函数的可重入性

信号之函数的可重入性 在调用某个函数过程中出现中断信号&#xff0c;且改信号处理函数中再次调用该函数&#xff0c;访问全局、静态变量的函数是不可重入函数。 前后数据不一致&#xff0c;函数是不可重入的&#xff0c;特点&#xff1a;函数中使用全局变量或静态变量。 前后数…

ASP.NET 中处理页面“回退”的方法

我们在编写基于 ASP.NET 的应用程序时&#xff0c;如果代码执行出错或检测到异常&#xff0c;一般会提示用户“返回”或“回退”&#xff0c;或者在多步操作、列表/详细的查看界面中&#xff0c;也会给用户提供回退到上一页面的链接&#xff0c;对于这种情况&#xff0c;大家很…

编辑器

1.FCKeditor 编辑器 FCKeditor is compatible with most internet browsers which include: IE 5.5 (Windows), Firefox 1.0, Mozilla 1.3 and Netscape 7. 最新版本:FCKeditor 2.0 语言环境:多国语言特性功能:所见所得,支持平台众多,支持XHTML 1.0,文本格式设置,常见的编辑,复…

VSTO---excel编程 [待续] [12月5日更新,详见文档下面]

最近比较闲了&#xff0c;考试也快要到了。但是编程技术方面还是不能掉啊.现在开始VSTO编程之旅了。这个话题是连Blog的。所以有兴趣的Blog之友&#xff0c;技术之士&#xff0c;可以匿名或者非匿名的评论&#xff0c;交流。对了&#xff0c;下面的代码都是本人写的&#xff0c…

Rose与PowerDesigner:两款建模工具对比分析比较

一、 二者的出身 作为世界最著名的两大CASE工具&#xff0c;Rational Rose和PowerDesigner的名声可谓如雷贯耳。Rose是当时全球最大的CASE工具提供商Rational的拳头产品&#xff0c;UML建模语言就是由Rational公司的三位巨头Booch、Rumbaugh和Jacobson发明的&#xff0c;后来R…

c#活动目录操作

添加引用 System.DirectoryServices导入命名空间 using System.DirectoryServices;srvip "192.168.1.1"; dn "DCl,DCcom";user "administrator"; pwd "123"; DirectoryEntry de;denewDirectoryEntry("LDAP://"srvip &quo…

[导入]Ajax使用初步

文章来源:http://blog.csdn.net/21aspnet/archive/2007/03/19/1534299.aspx 转载于:https://www.cnblogs.com/zhaoxiaoyang2/archive/2007/03/20/816309.html

CodeForces 570B,C

CodeForces 570B 题意&#xff1a;给定n和m&#xff0c;然后再&#xff08;1-n&#xff09;中随机取出c&#xff0c;求一个m使得 的概率最大&#xff0c;概率一样时输出最小的m。 思路&#xff1a;只需要看1到m-1和m1和n的最大的那一边就可以了&#xff0c;坑是n1的情况和n为…