最小堆定时器

完整代码在最下方

复制代码
最小堆:
1. 是完全二叉树
2. 当前节点的值总是小于等于它的子节点
3. 堆中的任意一个子树都是最小堆

mh-timer.c (主程序)
├── 调用 mh-timer.h (定时器接口层)
│   ├── add_timer / del_timer
│   ├── find_nearest_expire_timer
│   └── expire_timer
│
└── 依赖 minheap.h / minheap.c (最小堆实现)
    ├── min_heap_push_ (插入)
    ├── min_heap_pop_ (删除堆顶)
    ├── min_heap_erase_ (删除任意元素)
    └── min_heap_shift_up/down (堆化调整)

将定时器按超时时间存储在最小堆中,堆顶始终是最早到期的定时器。事件循环通过 epoll_wait 的超时参数实现精确等待,到期后批量触发。

最小堆不是二叉搜索树,因为它不维护左右子树的大小关系,只维持父子大小关系,"父节点值小于或等于任意一个子节点"。

最小堆可以使用数组实现,因为最小堆可以满足,在数组中索引为k的结点,

其父节点索引为(k - 1) / 2,

它的左孩子索引为left_child = 2 * k+ 1 ,

右孩子索引为right_child = 2 * k+ 2 。

所以使用此特性可以借助数组实现对元素的存储。

对于任务结点,需要的元素有 任务索引,任务定时时间,回调函数,任务上下文。

对于最小堆结点,需要一个任务指针数组,最小堆的容量,最小堆中的元素个数。

复制代码
struct timer_entry_s {
    uint32_t time;            // 定时器触发时间
    uint32_t min_heap_idx;    // 在最小堆中的索引位置,用于快速查找和删除
    timer_handler_pt handler; // 定时器回调函数指针
    void *privdata;           // 用户私有数据指针(上下文),回调时传递给handler
};


typedef struct min_heap {
    timer_entry_t **p; // 指针数组,存放 timer_entry* 指针
    uint32_t n, a; // n 为实际元素个数  a 为容量
} min_heap_t;

暴露给用户的接口

复制代码
void init_timer() 
timer_entry_t * add_timer(uint32_t msec, timer_handler_pt callback)
int find_nearest_expire_timer() 
void expire_timer()

初始化最小堆,将最小堆结构体清空。

复制代码
void init_timer() {
    min_heap_ctor_(&min_heap); // 初始化最小堆结构
}
void min_heap_ctor_(min_heap_t* s) { s->p = 0; s->n = 0; s->a = 0; }

添加定时器任务,申请任务结点内存,然后设置回调函数以及定时时间,压入堆中。

复制代码
timer_entry_t * add_timer(uint32_t msec, timer_handler_pt callback) {
    timer_entry_t *te = (timer_entry_t *)malloc(sizeof(*te));
    if (!te) {
        return NULL;
    }
    memset(te, 0, sizeof(timer_entry_t));

    te->handler = callback;
    te->time = current_time() + msec;

    if (0 != min_heap_push_(&min_heap, te)) {
        free(te); // 如果添加到最小堆失败,释放分配的内存
        return NULL;
    }
    printf("add timer time = %u now = %u\n", te->time, current_time());
    return te;
}

删除任务结点,先将队尾元素移动到需要删除的结点位置,然后再调整最小堆的结构体以满足最小堆的性质。

复制代码
bool del_timer(timer_entry_t *e) {
    return 0 == min_heap_erase_(&min_heap, e);
}
/**
 * @brief 从最小堆中删除指定的定时器条目。
 *
 * 该函数通过将堆尾元素移动到被删除元素的位置,然后根据堆的性质进行上移或下移调整,
 * 以保持最小堆的性质。
 *
 * @param s 指向最小堆结构体的指针。
 * @param e 指向待删除的定时器条目的指针。
 * @return int 成功删除返回 0;如果条目不在堆中(索引为 -1),则返回 -1。
 */
int min_heap_erase_(min_heap_t* s, timer_entry_t* e)
{
    if (-1 != e->min_heap_idx)
    {
        /* 取出堆尾元素,并减少堆的大小 */
        timer_entry_t *last = s->p[--s->n];
        /* 计算被删除元素父节点的索引 */
        unsigned parent = (e->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->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], last))
            min_heap_shift_up_unconditional_(s, e->min_heap_idx, last);
        else
            /* 否则,执行下移操作以恢复堆序性质 */
            min_heap_shift_down_(s, e->min_heap_idx, last);
        /* 将被删除元素的堆索引标记为无效(-1) */
        e->min_heap_idx = -1;
        return 0;
    }
    /* 元素不在堆中,返回 -1 */
    return -1;
}

查找堆顶结点的定时时间是否到期,返回定时器剩余时间。

复制代码
/**
 * @brief 查找最近即将到期的定时器剩余时间。
 *
 * 该函数从最小堆中获取堆顶元素(即最近到期的定时器),
 * 计算其到期时间与当前时间的差值。
 *
 * @return int 返回最近定时器的剩余时间(毫秒或特定单位)。
 *             如果定时器已到期或差值为负,返回 0。
 *             如果堆为空(无定时器),返回 -1。
 */
int find_nearest_expire_timer() {
    /* 获取最小堆堆顶的定时器条目 */
    timer_entry_t *te = min_heap_top_(&min_heap);
    /* 若堆为空,返回 -1 表示无定时器 */
    if (!te) return -1;
    /* 计算定时器到期时间与当前时间的差值 */
    int diff = (int) te->time - (int)current_time();
    /* 返回剩余时间,若已到期则返回 0 */
    return diff > 0 ? diff : 0;
}

处理到期的任务,使用循环,如果到期就执行回调函数,然后移除该任务,并释放该任务;如果未到期就退出循环。

复制代码
/**
 * @brief 处理所有已到期的定时器任务。
 *
 * 遍历定时器最小堆,依次取出堆顶的到期任务并执行其回调函数,
 * 直到堆为空或最近的任务尚未到期为止。
 */
void expire_timer() { // expire 失败,终止,到期
    uint32_t cur = current_time(); // 获取当前系统时间
    for (;;) {
        timer_entry_t *te = min_heap_top_(&min_heap); // 获取堆顶的定时器任务(最近将到期的任务)
        if (!te) break; // 若堆为空,则退出循环
        if (te->time > cur) break; // 若最近的任务尚未到期,则退出循环
        te->handler(te); // 执行定时器任务的回调处理函数
        min_heap_pop_(&min_heap); // 从堆中移除已处理的任务
        free(te); // 释放定时器任务对象的内存
    }
}

该定时器配合epoll使用,既可以定时又可以处理网络任务。

如果不使用epoll_wait阻塞等待,程序会一直空转,一直占用CPU资源。引入epoll_wait可以阻塞进程,让CPU去处理其他事情。

mh-timer.c

c 复制代码
#include <stdio.h>
#include <sys/epoll.h>
#include "mh-timer.h"

void hello_world(timer_entry_t *te) {
    printf("hello world time = %u\n", te->time);
}
// 统一的事件循环
while (!quit) {
    // 1. 计算最近定时器超时时间
    int timeout = find_nearest_timer();
    
    // 2. 等待 I/O 事件或超时
    int n = epoll_wait(epfd, events, MAX_EVENTS, timeout);
    
    // 3. 处理就绪的 I/O 事件
    for (int i = 0; i < n; i++) {
        handle_io_event(&events[i]);
    }
    
    // 4. 处理到期的定时器
    expire_timers();
}
int main() {
    init_timer();
    add_timer(3000, hello_world);

    int epfd = epoll_create(1);
    struct epoll_event events[512];

    for (;;) {
        // 1. 计算最近定时器超时时间
        int nearest = find_nearest_expire_timer();
        // nearest = -1  → epoll_wait 无限阻塞(无定时器)
        // nearest = 0   → epoll_wait 立即返回(有定时器已到期)
        // nearest > 0   → epoll_wait 阻塞 nearest 毫秒

        // 2. 等待 I/O 事件或超时
        int n = epoll_wait(epfd, events, 512, nearest);

        // 3. 处理就绪的 I/O 事件
        for (int i=0; i < n; i++) {
            // 处理 I/O 事件(本例中为空)
        }

        // 4. 处理到期的定时器
        expire_timer();
    }
    return 0;
}

// gcc mh-timer.c minheap.c -o mh -I./

mh-timer.h

c 复制代码
#ifndef MARK_MINHEAP_TIMER_H
#define MARK_MINHEAP_TIMER_H

#if defined(__APPLE__)
#include <AvailabilityMacros.h>
#include <sys/time.h>
#include <mach/task.h>
#include <mach/mach.h>
#else
#include <time.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>

#include "minheap.h"

static min_heap_t min_heap;

static uint32_t
current_time() {
    uint32_t t;
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
    struct timespec ti;
    clock_gettime(CLOCK_MONOTONIC, &ti); // 获取单调时钟时间,不受系统时间修改影响
    t = (uint32_t)ti.tv_sec * 1000;      // 将秒转换为毫秒
    t += ti.tv_nsec / 1000000;           // 将纳秒转换为毫秒并累加
#else // 老版本macOS不支持clock_gettime,使用gettimeofday作为回退方案
    struct timeval tv;
    gettimeofday(&tv, NULL);             // 获取当前时间(可能受系统时间修改影响)
    t = (uint32_t)tv.tv_sec * 1000;      // 将秒转换为毫秒
    t += tv.tv_usec / 1000;              // 将微秒转换为毫秒并累加
#endif
    return t;
}


void init_timer() {
    min_heap_ctor_(&min_heap); // 初始化最小堆结构
}

timer_entry_t * add_timer(uint32_t msec, timer_handler_pt callback) {
    timer_entry_t *te = (timer_entry_t *)malloc(sizeof(*te));
    if (!te) {
        return NULL;
    }
    memset(te, 0, sizeof(timer_entry_t));

    te->handler = callback;
    te->time = current_time() + msec;

    if (0 != min_heap_push_(&min_heap, te)) {
        free(te); // 如果添加到最小堆失败,释放分配的内存
        return NULL;
    }
    printf("add timer time = %u now = %u\n", te->time, current_time());
    return te;
}

bool del_timer(timer_entry_t *e) {
    return 0 == min_heap_erase_(&min_heap, e);
}
/**
 * @brief 查找最近即将到期的定时器剩余时间。
 *
 * 该函数从最小堆中获取堆顶元素(即最近到期的定时器),
 * 计算其到期时间与当前时间的差值。
 *
 * @return int 返回最近定时器的剩余时间(毫秒或特定单位)。
 *             如果定时器已到期或差值为负,返回 0。
 *             如果堆为空(无定时器),返回 -1。
 */
int find_nearest_expire_timer() {
    /* 获取最小堆堆顶的定时器条目 */
    timer_entry_t *te = min_heap_top_(&min_heap);
    /* 若堆为空,返回 -1 表示无定时器 */
    if (!te) return -1;
    /* 计算定时器到期时间与当前时间的差值 */
    int diff = (int) te->time - (int)current_time();
    /* 返回剩余时间,若已到期则返回 0 */
    return diff > 0 ? diff : 0;
}


/**
 * @brief 处理所有已到期的定时器任务。
 *
 * 遍历定时器最小堆,依次取出堆顶的到期任务并执行其回调函数,
 * 直到堆为空或最近的任务尚未到期为止。
 */
void expire_timer() { // expire 失败,终止,到期
    uint32_t cur = current_time(); // 获取当前系统时间
    for (;;) {
        timer_entry_t *te = min_heap_top_(&min_heap); // 获取堆顶的定时器任务(最近将到期的任务)
        if (!te) break; // 若堆为空,则退出循环
        if (te->time > cur) break; // 若最近的任务尚未到期,则退出循环
        te->handler(te); // 执行定时器任务的回调处理函数
        min_heap_pop_(&min_heap); // 从堆中移除已处理的任务
        free(te); // 释放定时器任务对象的内存
    }
}

#endif // MARK_MINHEAP_TIMER_H

minheap.c

c 复制代码
#include "minheap.h"

#define min_heap_elem_greater(a, b) \
    ((a)->time > (b)->time)
// a->time 比 b->time 大, 则返回 true (非零), 否则返回 false (0)
void min_heap_ctor_(min_heap_t* s) { s->p = 0; s->n = 0; s->a = 0; }
void min_heap_dtor_(min_heap_t* s) { if (s->p) free(s->p); }
void min_heap_elem_init_(timer_entry_t* e) { e->min_heap_idx = -1; }
int min_heap_empty_(min_heap_t* s) { return 0u == s->n; }
unsigned min_heap_size_(min_heap_t* s) { return s->n; }
timer_entry_t* min_heap_top_(min_heap_t* s) { return s->n ? *s->p : 0; }

/**
 * @brief 将定时器元素压入最小堆。
 *
 * 该函数将指定的定时器元素添加到最小堆中,并在必要时扩展堆的容量。
 * 新元素会被放置在堆底,然后执行上浮操作以维护最小堆性质。
 *
 * @param s 指向最小堆结构体的指针。
 * @param e 指向待插入的定时器条目的指针。
 * @return 成功时返回 0;如果内存扩展失败,则返回 -1。
 */
int min_heap_push_(min_heap_t* s, timer_entry_t* e)
{
    /* 尝试扩展堆容量以容纳新元素 (当前元素数量 + 1),失败则返回 -1 */
    if (min_heap_reserve_(s, s->n + 1))
        return -1;
    /* 将新元素放入堆底并执行上浮操作,同时增加堆的元素计数 */
    min_heap_shift_up_(s, s->n++, e);
    return 0;
}

/**
 * @brief 从最小堆中弹出堆顶元素(最小元素)。
 * 
 * 该函数移除并返回最小堆的堆顶元素。弹出后,堆的大小减一,
 * 并重新调整堆结构以维持最小堆性质。被弹出的元素的堆索引将被重置为无效值。
 * 
 * @param s 指向最小堆结构体的指针。
 * @return 返回堆顶元素的指针;如果堆为空,则返回 0 (NULL)。
 */
timer_entry_t* min_heap_pop_(min_heap_t* s)
{
    if (s->n) /* 检查堆中是否有元素 (s->n > 0) */
    {
        timer_entry_t* e = *s->p; /* 获取堆顶元素指针 (即数组首元素) */
        min_heap_shift_down_(s, 0u, s->p[--s->n]); /* 将堆末尾元素移至堆顶并下滤调整堆结构,同时堆大小减一 */
        e->min_heap_idx = -1; /* 将弹出元素的堆索引标记为无效 (-1) */
        return e; /* 返回弹出的堆顶元素 */
    }
    return 0; /* 堆为空,返回 0 (NULL) */
}

int min_heap_elt_is_top_(const timer_entry_t *e)
{
    return e->min_heap_idx == 0;
}

/**
 * @brief 从最小堆中删除指定的定时器条目。
 *
 * 该函数通过将堆尾元素移动到被删除元素的位置,然后根据堆的性质进行上移或下移调整,
 * 以保持最小堆的性质。
 *
 * @param s 指向最小堆结构体的指针。
 * @param e 指向待删除的定时器条目的指针。
 * @return int 成功删除返回 0;如果条目不在堆中(索引为 -1),则返回 -1。
 */
int min_heap_erase_(min_heap_t* s, timer_entry_t* e)
{
    if (-1 != e->min_heap_idx)
    {
        /* 取出堆尾元素,并减少堆的大小 */
        timer_entry_t *last = s->p[--s->n];
        /* 计算被删除元素父节点的索引 */
        unsigned parent = (e->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->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], last))
            min_heap_shift_up_unconditional_(s, e->min_heap_idx, last);
        else
            /* 否则,执行下移操作以恢复堆序性质 */
            min_heap_shift_down_(s, e->min_heap_idx, last);
        /* 将被删除元素的堆索引标记为无效(-1) */
        e->min_heap_idx = -1;
        return 0;
    }
    /* 元素不在堆中,返回 -1 */
    return -1;
}

/**
 * @brief 调整最小堆中指定元素的位置,或将其压入堆中。
 *        如果元素不在堆中,则执行压入操作;否则,根据需要向上或向下调整以维护堆性质。
 * @param s 指向最小堆结构体的指针。
 * @param e 指向需要调整的定时器条目的指针。
 * @return int 如果元素被压入堆中,返回压入操作的结果;如果元素位置被调整,返回 0。
 */
int min_heap_adjust_(min_heap_t *s, timer_entry_t *e)
{
    /* 检查元素是否不在堆中(索引为 -1 表示不在堆中) */
    if (-1 == e->min_heap_idx) {
        /* 元素不在堆中,执行压入操作 */
        return min_heap_push_(s, e);
    } else {
        /* 计算父节点的索引 */
        unsigned parent = (e->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->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], e))
            min_heap_shift_up_unconditional_(s, e->min_heap_idx, e);
        else
            /* 否则,向下调整以维护堆性质 */
            min_heap_shift_down_(s, e->min_heap_idx, e);
        return 0;
    }
}

/**
 * @brief 确保最小堆的底层数组容量足够容纳指定数量的元素。
 *        如果当前容量不足,则按指数策略扩容(通常翻倍,最小为8)。
 * @param s 指向最小堆结构体的指针。
 * @param n 需要预留的元素数量。
 * @return 成功返回 0;若内存重分配失败则返回 -1。
 */
int min_heap_reserve_(min_heap_t* s, unsigned n)
{
    if (s->a < n)
    {
        timer_entry_t** p;
        // 计算新容量:若当前容量为0则初始化为8,否则容量翻倍
        unsigned a = s->a ? s->a * 2 : 8;
        // 如果计算出的新容量仍小于所需容量,则直接使用所需容量
        if (a < n)
            a = n;
        // 尝试重新分配内存,失败则返回 -1
        if (!(p = (timer_entry_t**)realloc(s->p, a * sizeof *p)))
            return -1;
        // 更新堆的数组指针和容量
        s->p = p;
        s->a = a;
    }
    return 0;
}

void min_heap_shift_up_unconditional_(min_heap_t* s, unsigned hole_index, timer_entry_t* e)
{
    unsigned parent = (hole_index - 1) / 2;
    do
    {
    (s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index;
    /*
    1. 先执行 s->p[hole_index] = s->p[parent]
    2. 将 s->p[parent] 的 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)->min_heap_idx = hole_index;
}

void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, timer_entry_t* e)
{
    unsigned parent = (hole_index - 1) / 2;
    while (hole_index && min_heap_elem_greater(s->p[parent], e))
    {
    (s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index;
    hole_index = parent;
    parent = (hole_index - 1) / 2;
    }
    (s->p[hole_index] = e)->min_heap_idx = hole_index;
}

void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, timer_entry_t* 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])->min_heap_idx = hole_index;
    hole_index = min_child;
    min_child = 2 * (hole_index + 1);
    }
    (s->p[hole_index] = e)->min_heap_idx = hole_index;
}

minheap.h

c 复制代码
#ifndef MARK_MINHEAP_H
#define MARK_MINHEAP_H

#include <stdint.h>
#include <stdlib.h>

typedef struct timer_entry_s timer_entry_t;
typedef void (*timer_handler_pt)(timer_entry_t *ev);

struct timer_entry_s {
    uint32_t time;            // 定时器触发时间
    uint32_t min_heap_idx;    // 在最小堆中的索引位置,用于快速查找和删除
    timer_handler_pt handler; // 定时器回调函数指针
    void *privdata;           // 用户私有数据指针(上下文),回调时传递给handler
};


typedef struct min_heap {
    timer_entry_t **p; // 指针数组,存放 timer_entry* 指针
    uint32_t n, a; // n 为实际元素个数  a 为容量
} min_heap_t;

/* 构造函数:初始化最小堆结构体 */
void            min_heap_ctor_(min_heap_t* s);
/* 析构函数:销毁最小堆,释放相关资源 */
void            min_heap_dtor_(min_heap_t* s);
/* 初始化堆元素:设置元素的初始状态(通常将元素在堆中的索引初始化为 -1) */
void            min_heap_elem_init_(timer_entry_t* e);
/* 判断给定元素是否为堆顶元素 */
int             min_heap_elt_is_top_(const timer_entry_t *e);
/* 判断堆是否为空 */
int             min_heap_empty_(min_heap_t* s);
/* 获取堆中当前元素的数量 */
unsigned        min_heap_size_(min_heap_t* s);
/* 获取堆顶元素(即最小值元素),但不移除 */
timer_entry_t*  min_heap_top_(min_heap_t* s);
/* 预分配空间:调整堆的容量以容纳至少 n 个元素 */
int             min_heap_reserve_(min_heap_t* s, unsigned n);
/* 入堆:将元素 e 压入堆中,并自动调整堆序 */
int             min_heap_push_(min_heap_t* s, timer_entry_t* e);
/* 出堆:弹出并返回堆顶元素(最小元素) */
timer_entry_t*  min_heap_pop_(min_heap_t* s);
/* 调整元素:当元素 e 的键值发生变化时,重新调整其在堆中的位置以恢复堆性质 */
int             min_heap_adjust_(min_heap_t *s, timer_entry_t* e);
/* 删除元素:从堆中移除任意指定元素 e */
int             min_heap_erase_(min_heap_t* s, timer_entry_t* e);
/* 内部函数:从指定空洞位置向上调整(上滤),将元素 e 插入到合适位置 */
void            min_heap_shift_up_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);
/* 内部函数:无条件向上调整(通常用于已知元素肯定需要上移的优化场景) */
void            min_heap_shift_up_unconditional_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);
/* 内部函数:从指定空洞位置向下调整(下滤),将元素 e 插入到合适位置 */
void            min_heap_shift_down_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);

/*
void            min_heap_ctor_(min_heap_t* s);
void            min_heap_dtor_(min_heap_t* s);
void            min_heap_elem_init_(timer_entry_t* e);
int             min_heap_elt_is_top_(const timer_entry_t *e);
int             min_heap_empty_(min_heap_t* s);
unsigned        min_heap_size_(min_heap_t* s);
timer_entry_t*  min_heap_top_(min_heap_t* s);
int             min_heap_reserve_(min_heap_t* s, unsigned n);
int             min_heap_push_(min_heap_t* s, timer_entry_t* e);
timer_entry_t*  min_heap_pop_(min_heap_t* s);
int             min_heap_adjust_(min_heap_t *s, timer_entry_t* e);
int             min_heap_erase_(min_heap_t* s, timer_entry_t* e);
void            min_heap_shift_up_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);
void            min_heap_shift_up_unconditional_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);
void            min_heap_shift_down_(min_heap_t* s, unsigned hole_index, timer_entry_t* e);
*/
#endif // MARK_MINHEAP_H

执行命令

gcc mh-timer.c minheap.c -o mh -I./

相关推荐
Lumos_7772 小时前
Linux -- 线程
java·jvm·算法
七颗糖很甜2 小时前
“十五五”气象发展规划:聚焦五大核心任务
大数据·python·算法
科研前沿2 小时前
镜像视界浙江科技有限公司的关键技术突破有哪些?
大数据·人工智能·科技·算法·音视频·空间计算
嫩萝卜头儿2 小时前
2 - 复杂度收尾 + 链表经典OJ
数据结构·算法·链表·复杂度
星马梦缘3 小时前
算法设计与分析 作业二 答案与解析
算法·图论·dfs·bfs·floyd-warshall·bellman_ford·多源最短路
玛丽莲茼蒿3 小时前
Leetcode hot100 每日温度【中等】
算法·leetcode·职场和发展
cjp5603 小时前
009.UG二次开发,任务环境草图优化3(高级功能生成直线)
算法
样例过了就是过了3 小时前
LeetCode热题100 分割等和子集
数据结构·c++·算法·leetcode·动态规划
逻辑驱动的ken3 小时前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法