最小堆的数组实现

简介: 最小堆的数组实现

堆是一棵完全二叉树,之所以需要堆,是因为我们需要堆序性,堆的父节点都大于或小于其子节点,这样的有序性能让我们快速找到最大值或最小值,即根节点,时间复杂度是O(1)

由于完全二叉树的特殊性,我们可以使用数组表示一棵完全二叉树,而不是链表,这样做能获得最大的索引效率,缺点是树的大小不能动态增长

对于简单的需求,数组实现的堆可以达到最佳效率:

堆结构体:

typedef struct heap {
    int current_size;
    int max_size;
    char *h;
} heap_t;

堆的实现很简单,一个数组h,数组的大小max_size,以及当前已使用的大小current_size

初始化堆:

int init_heap(heap_t *heap, int size) {
    if (size <= 0) 
        size = 8;
    
    heap = (heap_t *)malloc(sizeof(heap_t) + size * sizeof(char));
    if (!heap) return -1;
    heap->h = heap++;
    heap->current_size = 0;
    heap->max_size = size;
    heap->h[0] = '0'; // 占用数组第一个位置0,便于insert和delete函数的索引
    return 0;
}

初始化一个堆,就是为堆结构体对象和其数组成员申请空间

需要留意的是数组第一个位置0是不用做存储节点的,原因是0用作索引计算起来不方便,后面会介绍

现在已经有了一个空堆,我们可以做的无非两种操作:

1.向堆中添加一个节点

2.从堆中取出一个节点(根节点)

由于随意添加和删除节点会破坏堆的性质,也就是堆序性,因此在插入和删除操作时,我们要确保操作完成后,堆的性质不变

1.插入

应该从哪里插入呢?由于插入一个节点后数组会多出一个元素(插入前为空位),因此我们采用类似冒泡的方法,将这个空位上滤,所有大于待插入节点的父节点都会被下滤,最后我们得到的空位就是待插入节点正确的位置:

void insert(heap_t *heap, char c) { //  上滤,插入一个节点
    if (if_full(heap)) {
        printf("heap is full !\n");
        return;
    }
    int i;     // 根据大小遍历交换空位与其父节点
    for (i = ++heap->current_size; heap->h[i] > c; i /= 2)
        heap->h[i] = heap->h[i/2];
    heap->h[i] = c; // 此时的 i 大于其父节点,小于其所有子节点
    heap->current_size++;
}

需要复习的是,对于完全二叉树的一个节点i,其父节点为2i,左子节点为 i / 2,右子节点为(i / 2)+ 1

2.删除根节点

删除根节点也就是取出最大(小)值节点,由于删除后数组少了一个元素,而且是第一个元素,需要一个新的最小值节点 作为根节点。同时要将最后一个元素更换位置,以确保完全二叉树的形式

char delete_min(heap_t *heap) {
    
    if (is_empty(heap)) {
        printf("heap is empty !\n");
        return heap->h[0];
    }
    int i, child;
    char min, last;
    min = heap->h[1];
    last = heap->h[heap->current_size--];
    
    for (i = 1; i * 2 <= heap->current_size; i = child) { 
        
        // 找出较小的子节点
        child = i * 2;     //  左子节点
        if (child != heap->current_size && heap->h[child+1] < heap->h[child])
            child ++;      //  右子节点
        
        if (last > heap->h[child])    
            heap->h[i] =  heap->h[child]; // 交换父子节点,父节点是一个空位
        else 
            break; // 子节点大于last节点
    }
    heap->h[i] = last; //  退出循环的两种情况:1. i的子节点是叶子节点 2. i的子节点大于最后一个节点last
    return min;
}

推荐学习 https://xxetb.xetslk.com/s/p5Ibb

目录
相关文章
|
Java Maven Windows
Windows 配置Maven的本地仓库和阿里云远程中央仓库
Windows 配置Maven的本地仓库和阿里云远程中央仓库
3197 1
Windows 配置Maven的本地仓库和阿里云远程中央仓库
|
8月前
|
druid Java 关系型数据库
Spring Boot与Druid升级解决方案
好的,我需要帮助用户解决他们遇到的数据库连接问题,并升级项目的依赖。首先,用户提供的错误信息是关于Spring Boot应用在初始化数据源时抛出的异常,具体是Druid连接池验证连接失败。同时,用户希望升级项目的依赖版本。
766 10
|
机器学习/深度学习 并行计算 计算机视觉
CUDA:王者之巅——探究CUDA为何能成为并行计算的佼佼者
本文探讨了CUDA在并行计算领域的崛起及其成为佼佼者的原因,详细介绍了CUDA的技术背景、架构原理及在深度学习、图像处理等领域的应用案例,展示了其显著的性能优势与优化方法,并展望了CUDA在未来计算技术发展中的潜力与方向。
|
前端开发 JavaScript Linux
十年跨平台开发,Electron 凭什么占据一席之地?
本文首发于微信公众号“前端徐徐”。作者徐徐将系统整理Electron的相关知识,分享更多开发经验。Electron是一个已有10年历史的跨端开发框架,本文将从其诞生背景、优劣势、生态、案例等方面进行详细介绍,并与其他框架进行对比,帮助读者全面了解Electron。
941 2
十年跨平台开发,Electron 凭什么占据一席之地?
|
存储
外部排序快速入门详解:基本原理,败者树,置换-选择排序,最佳归并树
外部排序用于处理无法一次性加载到内存中的大规模数据排序问题。其基本原理是将外存数据划分为若干已内部排序的小块,利用内存中的缓冲区进行多路归并排序,并逐步合并以生成更大的有序块。通过增加缓冲区数量、优化关键字比较次数(如使用败者树)和调整归并段长度等方法可进一步提高排序效率。最佳归并树的应用则能有效减少磁盘I/O次数,从而优化整个排序过程。
1051 8
|
消息中间件 canal 缓存
Redis与MySQL双写一致性如何保证:延迟双删?binlog异步删除?
Redis与MySQL双写一致性如何保证:延迟双删?binlog异步删除?
|
缓存 运维 Docker
Docker清理磁盘空间
在日常运维当中,Docker会产生一些运行时的临时文件,我们需要定时的清理掉他们,不然将会对磁盘造成极大的压力。
458 0
|
存储 消息中间件 缓存
实现多级缓存架构设计方案
TMC,即“透明多级缓存(Transparent Multilevel Cache)”,是有赞 PaaS 团队给公司内应用提供的整体缓存解决方案。 TMC 在通用“分布式缓存解决方案(如 CodisProx
实现多级缓存架构设计方案
|
机器学习/深度学习 搜索推荐 Python
L2范数(L2 norm)
L2范数(L2 norm),也称为欧几里德范数(Euclidean norm)或2-范数,是向量元素的平方和的平方根。它在数学和机器学习中经常被用作一种正则化项、距离度量或误差度量。
11256 76
|
Java API Nacos
第十二章 Spring Cloud Alibaba Sentinel
第十二章 Spring Cloud Alibaba Sentinel
1559 0

热门文章

最新文章