1. 程式人生 > >【高效能定時器】時間堆(最小堆)

【高效能定時器】時間堆(最小堆)

最小堆及其應用:時間堆

一、 堆

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個,因此只要保證這些節點構成的子樹具有堆性質,就能保證整棵樹具有堆性質。(因為非葉子結點的下濾會將葉子節點也變的具有堆性質)

1 3 2 6 5 4 7

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

相關推薦

高效能定時時間

最小堆及其應用:時間堆 一、 堆 1. 概念 堆是一種經過排序的完全二叉樹,其中任一非終端節點的資料值均不大於(或不小於)其左子節點和右子節點的值。 其中,兩個葉子節點的大小沒有順序。 堆又分為兩種,最大堆、最小堆。由上面的

linux核心設計與實現 —— 定時時間管理第11章

核心中的時間概念 硬體為核心提供了一個系統定時器用以計算流逝的時間。系統定時器是一種可程式設計硬體晶片,它能以固定頻率產生中斷。該頻率可以通過程式設計預定,稱作節拍率(tick rate)。該中斷就是所謂的定時器中斷,它所對應的中斷處理程式負責更新系統時間,也

Bzoj3894文理分科

描述 擁有 pri const getch bzoj3 solution efi ini Description ?文理分科是一件很糾結的事情!(雖然看到這個題目的人肯定都沒有糾結過) ?小P所在的班級要進行文理分科。他的班級可以用一個n*m的矩陣進行描述,每個格子代表一個

CCF 201609-4交通規劃短路徑樹 Dijkstra

題目抽象 要求所有結點與源結點連通,使得所有邊權之和最小。即求最小的最短路徑樹。 大致思路 演算法:通過Dijkstra演算法可以構建最短路徑樹,如何保證這棵樹最小呢?這就需要一些變形了: 每個結點需要記錄它的前驅邊,每次鬆弛的條件是u.d + e ≤ v.d,

洛谷 P2754 [CTSC1999]家園大流

題目連結 突然發現Dinic很好寫誒。。 第一次陣列開小了,玄學\(WA\),what?資料範圍描述有誤? 分層圖,每天為一層。 把上一天的每個空間站向這一天連一條流量為inf的邊,表示可以原地不動。 把一個週期內上一天上一個和這一天這一個連一條流量為這艘太空船的容量的邊,表示去下一站。 然後每次加一天,看什

Spring 定時定時 No qualifying bean of type [org.springframework.scheduling.TaskScheduler] is defined

stackoverflow 版 http://stackoverflow.com/questions/31199888/spring-task-scheduler-no-qualifying-bean-of-type-org-springframework-sche

Spring 定時Spring 定時 No qualifying bean of type [org.springframework.scheduling.TaskScheduler]

Spring 定時器 No qualifying bean of type [org.springframework.scheduling.TaskScheduler] is defined 最近專案裡面,用了spring的定時任務,一直以來,專案執行的不錯。定時器

排序--演算法導論

堆排序的思想在堆排序(最大堆)已做說明,故不再贅述; 總之,思想就是首先進行建堆,由於這是最小堆,故而必須保證父節點都小於孩子節點,若不滿足條件,則進行調節; 最後進行堆排序,不斷將最小的提取出來,並對剩下的進行調節,使之滿足最小堆; 故而將最大堆中的判斷父節點與孩子大小部

Linux Kernel 定時時間管理淺析

前言 計算機時間: 這個三維的世界就是由時間,空間,物質構成的,即使是計算機也離不開時間這個東西,時間之於計算機舉足輕重。 計算機的時間 時間管理在核心中佔有很重要的地位。相對於事件驅動而言,核心中有大量的函式都是基於時間驅動的。 這裡先說幾個重要的概念: 相對時間: 假設某

並查集通暢工程航電1232

通過 統計 iostream clu 連接 tail efi pac des 轉自:http://blog.csdn.net/dellaserss/article/details/7724401/(並查集的講解非常有趣) Problem Description 某

網絡流24題圓桌聚餐大流

cpp pan pos har pre cst ++ get post 【網絡流24題】圓桌聚餐(最大流) 題面 Cogs 題解 這道題很簡單 首先每個單位的人數限制 直接從源點向單位連邊,容量為人數 同樣的, 每個桌子向匯點連邊,容量為可以坐的人數 因為每個桌子只能夠做一

大話存儲學習筆記7,8章,FC協議

pass 混亂 fff 區分 san 主機 所有 內容 發生 Fibre Channnel 我們之前引入了SAN的概念,SAN首先是個網絡,而不是存儲設備。這個網絡是專門來給主機連接存儲設備用的。 我們知道按照SCSI總線16個節點的限制,不可能接入很多的磁盤,要擴大SAN

算法學習Fhq-Treap無旋Treap

遍歷 功能 很多 www 代碼 二叉查找樹 說明 命名 例題 Treap——大名鼎鼎的隨機二叉查找樹,以優異的性能和簡單的實現在OIer們中廣泛流傳。 這篇blog介紹一種不需要旋轉操作來維護的Treap,即無旋Treap,也稱Fhq-Treap。 它的巧妙之處在於只需要分

Usaco2008 Oct灌水 小生成樹

算法 操作 while 進行 題目 nbsp IT farm ostream 題目描述 Farmer John已經決定把水灌到他的n(1<=n<=300)塊農田,農田被數字1到n標記。把一塊土地進行灌水有兩種方法,從其他農田飲水,或者這塊土地建造水庫。 建造一

3dmax2013-20193dsmax破解版破解中文版付破解教程

3Dmax3dmax2013-2019【3dsmax破解版】破解中文版界面語言:中文版/英文版軟件大小:5.32GB運行環境:Win2003,WinXP,Win2000,Win9X,Win7運行支持:64位

2018.11.06NOIP2015洛谷P2668鬥地主DP預處理搜尋

傳送門 解析: 其實不考慮點數大小的話只有張數對我們是有用的。所以可以預處理出有 i i

2018.11.06SCOI2005BZOJ1087洛谷P1896互不侵犯狀壓DP

BZOJ傳送門 洛谷傳送門 解析: 範圍只有9,顯然是狀壓DP。 考慮處理出每個可能的狀態來減小常數。 然後列舉行,列舉當前行狀態,列舉前一行狀態,更新即可。 注意要預處理第一行的情況。 程式碼: #include<bits/stdc++

ArcGIS|空間分析選址分析為學校選址

派生資料>重分類>加權疊加>選擇最適宜區域>轉為向量確定道路和麵積確定最終位置 文章目錄 1、資料 2、分析思路 3、步驟 3.1 派生資料 3.2 對資料集進行重分類 3.3 為資料

SPOJ - SCITIESSelfish Cities費用大流

Far, far away there is a world known as Selfishland because of the nature of its inhabitants. Hard times have forced the cities of Selfishland to ex

POJ - 2135Farm Tour費用大流

When FJ's friends visit him on the farm, he likes to show them around. His farm comprises N (1 <= N <= 1000) fields numbered 1..N, the first o