最小堆的数组实现

简介: 最小堆的数组实现

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

目录
相关文章
|
存储 算法 索引
【二叉树】的顺序存储(堆的实现)
【二叉树】的顺序存储(堆的实现)
98 0
|
4月前
|
JavaScript 前端开发 索引
如何添加新元素到数组中
如何添加新元素到数组中
|
1月前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
39 1
|
6月前
|
存储 算法 Java
数组与链表
数组与链表
|
7月前
|
算法 C语言 索引
215. 数组中的第K个最大元素(时间复杂度o(n))
215. 数组中的第K个最大元素(时间复杂度o(n))
|
7月前
|
存储
三种方法实现获取链表中的倒数第n个元素
三种方法实现获取链表中的倒数第n个元素
71 0
|
7月前
|
存储 机器学习/深度学习 人工智能
4.线性表之数组
4.线性表之数组
81 0
|
存储 机器学习/深度学习 缓存
链表和有序二叉树插入元素时真的比数组快吗?
公司有位C++标准委员会的顾问大佬,一年会有几次视频讲座,分享一些编程要点或者经验。很多时候都是C++很基础的方面,但是他的讲解视频真的很深入浅出,有时候会“打破”一些理所应当的观点,这篇文章就是让我觉得很有趣,并且意想不到的地方,在这里分享一下。
链表和有序二叉树插入元素时真的比数组快吗?
|
存储 算法 前端开发
三刷”数组中的第K个最大元素“,我终于学会了堆排序
三刷”数组中的第K个最大元素“,我终于学会了堆排序
77 0
|
存储 NoSQL 算法
数组和链表
数组和链表
81 0