## 介绍 堆(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对象 ```c 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对象。创建方法和删除应该成对使用,创建出来在结束使用应该删除掉。 ```c 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的入堆和出堆 ```c int heap_push(heap_t heap, void *data); int heap_pop(heap_t heap, void *data); ``` 这两个方法可以很方便的把数据添加到堆和从堆弹出,`push`方法`data`传入需要入队数据的地址,`pop`方法`data`传入需要接收出队数据的地址,这两个方法`data`都可以传入NULL,那只是一个占位。操作成功返回1,失败返回0。 ```c 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的修改 ```c int heap_modify(heap_t heap, int index, void *data); ``` 用于修改堆中指定索引位置的元素数据,修改后会根据堆的性质重新调整堆的结构,确保堆依然保持正确的堆性质状态。若修改操作成功,函数返回1;若索引超出范围、堆不存在等情况导致修改无法进行,则返回0。 - `heap`:堆的句柄,通过它来定位要修改元素所在的具体堆,只有对应正确的堆才能准确找到并修改目标元素。 - `index`:要修改的元素在堆中的索引位置,索引从0开始计数,表示堆中元素的序号,通过该索引能准确找到需要修改的目标元素,不过要确保传入的索引值在堆的有效范围内(小于堆的当前元素个数)。 - `data`:指向新数据的指针,该指针指向的数据内容会替换掉堆中指定索引位置原有的元素数据,其数据类型和大小应与创建堆时规定的元素数据大小一致。 ```c 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的顶端 ```c int heap_top(heap_t heap, void *data); ``` 用于获取堆顶的元素数据,对于大根堆来说获取的是最大元素,对于小根堆则是最小元素。若获取操作成功,函数返回1,并且若`data`参数不为`NULL`,会将堆顶元素数据复制到该指针所指向的内存空间中;若堆为空或者出现其他导致获取失败的情况,则返回0。 - `heap`:堆的句柄,通过该句柄确定要获取堆顶元素的目标堆,不同的堆有各自独立的堆顶元素,需要准确指定对应的堆才能获取正确的堆顶数据。 - `data`:指向用于存储堆顶元素数据的内存空间的指针,若希望获取堆顶元素内容并存储起来便于后续使用,可传入有效的指针地址,函数会进行数据复制操作;若不需要获取具体数据,可传入`NULL`作为参数。 ### heap的大小 ```c int heap_size(heap_t heap); ``` 用于获取当前堆中元素的个数,返回的整数值表示堆中实际存储的元素数量,方便开发者了解堆的当前存储情况,比如判断堆是否为空、是否已满等。 - `heap`:堆的句柄,依据该句柄找到对应的堆数据结构,进而获取该堆所包含的元素个数信息,每个堆都有其独立的元素数量统计,通过句柄能准确区分不同堆的情况。