varch/doc/heap.md

7.4 KiB
Raw Blame History

介绍

Heap是一种特殊的完全二叉树具有特定的堆性质。堆主要分为两种类型最大堆和最小堆。

  1. 概念

    • 堆是一种完全二叉树,除了最后一层外,每一层都是满的,并且最后一层的节点尽可能地向左排列。
    • 堆中每个节点的值都满足堆性质,即在最大堆中,每个节点的值大于或等于其子节点的值;在最小堆中,每个节点的值小于或等于其子节点的值[^1^][^2^][^3^][^4^][^5^]。
  2. 结构

    • 堆通常通过数组来实现,因为堆是一棵完全二叉树,可以高效地映射到数组中。
    • 假设堆的根节点存储在数组的第一个位置索引为0那么对于任意一个节点其子节点和父节点的位置可以通过以下公式计算
      • 父节点索引:parent(i) = (i - 1) / 2
      • 左子节点索引:left(i) = 2i + 1
      • 右子节点索引:right(i) = 2i + 2[^2^][^4^]
  3. 完全二叉树:堆总是一棵完全二叉树,即除了最后一层外,所有层都是满的,且最后一层的节点尽可能地向左排列[^1^][^2^][^3^][^4^][^5^]。

  4. 堆序性质

    • 最大堆:每个节点的值大于或等于其子节点的值,这保证了堆顶(根节点)是整个堆中的最大值[^1^][^2^][^3^][^4^][^5^]。
    • 最小堆:每个节点的值小于或等于其子节点的值,这保证了堆顶(根节点)是整个堆中的最小值[^1^][^2^][^3^][^4^][^5^]。
  5. 高度对于包含n个节点的堆其高度为O(log n),这使得堆的许多操作具有对数时间复杂度[^4^]。

varch heap是一个用于C语言的通用堆容器模块。它定义了堆相关的数据类型以及一系列操作堆的函数接口涵盖了堆的创建、删除、元素入堆、出堆、修改、获取堆顶元素、获取堆大小等功能同时通过宏定义提供了获取堆中节点父节点、左右子节点索引的便捷方式方便开发者在C语言项目中运用堆这种数据结构来进行数据管理和操作例如实现优先级队列等应用场景。

接口

创建和删除heap对象

heap_t heap_create(int dsize, int capacity, heap_root_t root);
void heap_delete(heap_t heap);

其中heap_t为heap的结构体。

创建方法: 创建方法则会返回一个heap对象创建失败则返回NULL。

  • dsize:表示堆中每个元素的数据大小,以字节为单位,用于确定在内存中为每个元素分配的空间大小,比如存储一个包含多个成员的结构体元素时,该参数就应该设置为结构体的大小。
  • capacity:堆的初始容量,也就是堆在创建时最多能容纳的元素个数,当后续插入元素超过这个容量时,可能需要进行扩容等相关处理(具体取决于内部实现逻辑),开发者可以根据预估的元素数量合理设置该参数。
  • root:指向一个函数的指针,该函数用于定义堆的根节点类型规则,即通过比较父节点和子节点来确定堆的性质(大根堆或小根堆),传入的函数需要按照规定的返回值逻辑来实现,以便后续堆操作能依据正确的堆性质进行元素调整等操作。

删除方法: 删除传入的heap对象。创建方法和删除应该成对使用创建出来在结束使用应该删除掉。

static int heap_root_min(void *parent, void *child)
{
    if (*(int *)parent < *(int *)child) return 1;
    return 0;
}
static int heap_root_max(void *parent, void *child)
{
    if (*(int *)parent > *(int *)child) return 1;
    return 0;
}

static void test_create(void) 
{
    heap_t heap = heap_create(sizeof(int), 11, heap_root_max);
    
    if (heap)
    {
        printf("heap create success!!!\r\n");
    }
    else  
    {
        printf("[ERROR] heap create fail!!!\r\n");
    }

    heap_delete(heap);
}

heap的入堆和出堆

int heap_push(heap_t heap, void *data);
int heap_pop(heap_t heap, void *data);

这两个方法可以很方便的把数据添加到堆和从堆弹出,push方法data传入需要入队数据的地址,pop方法data传入需要接收出队数据的地址,这两个方法data都可以传入NULL那只是一个占位。操作成功返回1失败返回0。

static void test_push(void) 
{
    heap_t heap = heap_create(sizeof(int), 11, heap_root_max);
    int push = 0, top = 0;

    push = 100; heap_push(heap, &push); heap_top(heap, &top); printf("top = %d\r\n", top);
    push = 1;   heap_push(heap, &push); heap_top(heap, &top); printf("top = %d\r\n", top);
    push = 2;   heap_push(heap, &push); heap_top(heap, &top); printf("top = %d\r\n", top);
    push = 200; heap_push(heap, &push); heap_top(heap, &top); printf("top = %d\r\n", top);
    push = -10; heap_push(heap, &push); heap_top(heap, &top); printf("top = %d\r\n", top);

    heap_delete(heap);
}

heap的修改

int heap_modify(heap_t heap, int index, void *data);

用于修改堆中指定索引位置的元素数据修改后会根据堆的性质重新调整堆的结构确保堆依然保持正确的堆性质状态。若修改操作成功函数返回1若索引超出范围、堆不存在等情况导致修改无法进行则返回0。

  • heap:堆的句柄,通过它来定位要修改元素所在的具体堆,只有对应正确的堆才能准确找到并修改目标元素。
  • index要修改的元素在堆中的索引位置索引从0开始计数表示堆中元素的序号通过该索引能准确找到需要修改的目标元素不过要确保传入的索引值在堆的有效范围内小于堆的当前元素个数
  • data:指向新数据的指针,该指针指向的数据内容会替换掉堆中指定索引位置原有的元素数据,其数据类型和大小应与创建堆时规定的元素数据大小一致。
static void test_base(void)
{
    heap_t h = heap_create(sizeof(int), 11, heap_root_max);
    int i = 0;

    for (i = 0; i < 11; i++)
    {
        heap_push(h, &i);
    }
    
    i = -9;
    heap_modify(h, 6, &i);

    heap_delete(h);
}

heap的顶端

int heap_top(heap_t heap, void *data);

用于获取堆顶的元素数据对于大根堆来说获取的是最大元素对于小根堆则是最小元素。若获取操作成功函数返回1并且若data参数不为NULL会将堆顶元素数据复制到该指针所指向的内存空间中若堆为空或者出现其他导致获取失败的情况则返回0。

  • heap:堆的句柄,通过该句柄确定要获取堆顶元素的目标堆,不同的堆有各自独立的堆顶元素,需要准确指定对应的堆才能获取正确的堆顶数据。
  • data:指向用于存储堆顶元素数据的内存空间的指针,若希望获取堆顶元素内容并存储起来便于后续使用,可传入有效的指针地址,函数会进行数据复制操作;若不需要获取具体数据,可传入NULL作为参数。

heap的大小

int heap_size(heap_t heap);

用于获取当前堆中元素的个数,返回的整数值表示堆中实际存储的元素数量,方便开发者了解堆的当前存储情况,比如判断堆是否为空、是否已满等。

  • heap:堆的句柄,依据该句柄找到对应的堆数据结构,进而获取该堆所包含的元素个数信息,每个堆都有其独立的元素数量统计,通过句柄能准确区分不同堆的情况。