最小堆的数组实现

简介: 最小堆的数组实现

堆是一棵完全二叉树,之所以需要堆,是因为我们需要堆序性,堆的父节点都大于或小于其子节点,这样的有序性能让我们快速找到最大值或最小值,即根节点,时间复杂度是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的本地仓库和阿里云远程中央仓库
2865 1
Windows 配置Maven的本地仓库和阿里云远程中央仓库
|
Linux
LINUX下载编译fontconfig
LINUX下载编译fontconfig
698 0
|
Java API
Java高效找出两个大数据量List集合中的不同元素
本文将带你了解如何快速的找出两个相似度非常高的List集合里的不同元素。主要通过Java API、List集合双层遍历比较不同、借助Map集合查找三种方式,以及他们之间的执行效率情况。
2117 1
|
5月前
|
druid Java 关系型数据库
Spring Boot与Druid升级解决方案
好的,我需要帮助用户解决他们遇到的数据库连接问题,并升级项目的依赖。首先,用户提供的错误信息是关于Spring Boot应用在初始化数据源时抛出的异常,具体是Druid连接池验证连接失败。同时,用户希望升级项目的依赖版本。
465 10
|
开发工具 git
|
Ubuntu 安全 测试技术
Ubuntu 22.04 Samba 安装和配置
SMB(Server Message Block)是一种跨平台的文件共享协议,它允许不同操作系统之间的文件和打印机共享。在本文中,我们将详细介绍如何在 Ubuntu 服务器上部署和配置一个 SMB 服务器,并涵盖多通道配置、性能测试、安全最佳实践以及一些常见问题。【8月更文挑战第1天】
2155 1
|
SQL 监控 druid
springboot 集成Druid的监控数据库连接池的最佳实践
Druid是一种高性能的开源数据库连接池,它在Java应用程序中被广泛使用。Druid连接池提供了连接管理、连接池监控、SQL性能监控等功能,能够有效地管理数据库连接,并提供丰富的性能指标和监控报告。 Druid连接池的一些主要特点包括: 连接池管理:Druid可以帮助你管理数据库连接,包括连接的创建、销毁和重用。它提供了连接池配置选项,可以灵活地调整连接池的大小、最大等待时间、验证查询等参数。 监控数据统计:Druid连接池提供了丰富的监控指标,如连接数、活跃线程数、执行SQL次数、慢查询次数、错误次数等。通过这些统计数据,你可以实时了解连接池的使用情况和性能状况。 SQL性能监控:
3792 1
|
存储 编解码 NoSQL
四.Redis中那些你不知道的秘密-五大基本结构Hash的实现原理
Hash也是Redis中非常常用的一种存储结构了,Redis的hash底层用到了两种存储结构,ziplist压缩列表 和 hash 表,当存储的所有键值对的键和值的字符串长度都小于64字节,且元素个数少于512个,Redis会选择ziplist存储,这样会比较省内存,否则他会选择hashtable hash表去成,这里的hash表它底层结构和Java中的HashMap比较像,都是数组+链表,链表是为了解决hash冲突。
|
前端开发
Element ui 表格(Table)组件中前端实现数据分页和模糊查询
Element ui 表格(Table)组件中前端实现数据分页和模糊查询
986 0
Element ui 表格(Table)组件中前端实现数据分页和模糊查询
|
XML 缓存 Java
Spring Bean管理核心组件——后置处理器详解
Spring Bean管理核心组件——后置处理器详解