一、概念
1.定义
堆:堆在逻辑结构上可以看作一棵完全二叉树,并且任意结点比它的左、右孩子大(小),称为大(小)堆,或者大(小)根堆;根节点称为堆顶;每条路径一定是升序或者降序。
逻辑结构与存储结构:
2.堆的实现思路
2.1堆的创建
在给定的一个数组,在逻辑上看作一棵完全二叉树,我们可以通过从根结点开始向下调整算法把它调整成为一个堆,但利用向下调整算法有一个前提,那就是左右子树必须是一个堆,才能利用此算法进行调整。
结合二叉树的性质,我们可以通过从二叉树的倒数第一个非叶子结点的子树开始调整,一直调整到根结点的树,这样就可以把完全二叉树调整为一个堆了
2.2堆的插入
结合堆的创建与调整思想,堆的插入可以通过插入到数组的末尾,即完全二叉树的末尾,然后采用向上调整算法,直至完全二叉树满足堆的定义。
2.3堆的删除
删除堆是删除堆顶的数据,但我们若直接删除堆顶,那么堆就缺失了堆顶,完全二叉树没有了根结点,即该结构不再满足堆的定义。
正确的删除方法是,将堆顶元素与最后一个元素进行对换,然后删除最后一个元素,这样就完成了堆的删除操作,但是对换之后,堆顶可能不再满足堆的特性,此时还需要对堆顶进行向下调整,直至恢复成堆。
二、结构体定义
typedef int HPDataType; typedef int (*PFUNC)(HPDataType left, HPDataType right); typedef struct Heap { HPDataType* array; int size;//有效元素个数 int capacity;//容量 PFUNC pCompare;//大根堆or小根堆 回调函数 }Heap;
这里为了方便我们建立不同的堆,在结构体中增加了一个回调函数,具体实现原理在后续代码中。
三、接口实现
void HeapCreate(Heap* hp, HPDataType* array, int length, PFUNC pCompare);// 堆的构建 int Min(HPDataType right, HPDataType left);//创小根堆回调函数 int Max(HPDataType right, HPDataType left);//创大根堆回调函数 void AdjustDown_Heap(Heap* hp, int parent);//堆调整函数 void Swap(HPDataType* parent, HPDataType* child);//将结点与较大或较小的孩子进行交换 void HeapCheckCapacity(Heap* hp); //判满 & 扩容 int HeapEmpty(Heap* hp);// 堆的判空 void HeapPush(Heap* hp, HPDataType x);// 堆的插入 void HeapErase(Heap* hp);// 堆的删除 HPDataType HeapTop(Heap* hp);// 取堆顶的数据 int HeapSize(Heap* hp);// 堆的数据个数 void HeapDestroy(Heap* hp);// 堆的销毁 void print_Heap(Heap* hp);//打印函数 void Test_Heap();//测试函数
1.堆的构建
void HeapCreate(Heap* hp, HPDataType* array, int length, PFUNC pCompare) {// 堆的构建 assert(hp); hp->array = (HPDataType*)malloc(sizeof(HPDataType) * length); if (hp->array == NULL) {//申请失败 assert(0); return; } hp->capacity = length; memcpy(hp->array, array, sizeof(HPDataType) * length);//将数组内元素拷贝到堆中 hp->size = length; hp->pCompare = pCompare; for (int root = ((hp->size - 1) - 1) / 2; root >= 0; root--) {//对堆进行调整 AdjustDown_Heap(hp, root);//堆调整函数 } }
结合完全二叉树的特性,最后一个结点的双亲即倒数第一个非叶子结点;结点n的双亲结点等于(n-1)/2,因为size为堆的大小,所以其最后一个叶子结点的下标为size-1,所以代码中有两个减一操作。
然后通过向下调整算法从倒数第一个非叶子结点往回调整。
1.1向下调整函数
void AdjustDown_Heap(Heap* hp,int parent) {//堆调整函数 int child = parent * 2 + 1;//parent可能只有左没有右,所以标记左孩子 int size = hp->size; while (child < size) { if (child + 1 < size && hp->pCompare(hp->array[child + 1], hp->array[child])) {//找parent较小的孩子 child += 1; } //检测当前结点即孩子是否满足堆的特性 if (hp->pCompare(hp->array[child], hp->array[parent])) {//较小(较大)孩子比结点小(大),交换两个结点 Swap(&hp->array[parent], &hp->array[child]); parent = child;//交换后子树可能不满足小(大)堆,继续往下标记判断 child = parent * 2 + 1; } else {//当前结点满足堆特性,不需要继续向下调整 return; } } }
因为,双亲结点可能没有有孩子,所以我们标记双亲结点的左孩子,通过找出并标记较小(创小堆Min)或较大(创大堆Max)的孩子,再与根结点进行比较,判断当前结点是否需要调整。
2.堆的判满与扩容
void HeapCheckCapacity(Heap* hp) {//判满&扩容 assert(hp); if (hp->size == hp->capacity) { int newCapacity = hp->capacity * 2;//扩大为原容量两倍 HPDataType* temp = (HPDataType*)malloc(sizeof(HPDataType) * newCapacity); if (temp == NULL) {//申请失败 assert(0); return; } memcpy(temp, hp->array, sizeof(HPDataType) * hp->size);//将数据拷贝到新空间中 free(hp->array); hp->array = temp; hp->capacity = newCapacity; } }
3.堆的插入
void HeapPush(Heap* hp, HPDataType x) {// 堆的插入 assert(hp); HeapCheckCapacity(hp);//判满&自动扩容 hp->array[hp->size] = x;//插入到堆的末尾 hp->size++; AdjustUp_Heap(hp, hp->size - 1);//插入后需向上调整 }
将元素插入到末尾,采用向上调整算法进行调整至堆。
3.1向上调整函数
void AdjustUp_Heap(Heap* hp, int child) {//向上调整函数 int parent = (child - 1) / 2;//标记该结点的双亲结点 while (child) { if (hp->pCompare(hp->array[child], hp->array[parent])) {//该结点比双亲结点小(大) Swap(&hp->array[parent], &hp->array[child]);//较小的往上交换 child = parent;//标记双亲结点为新的child结点,继续向上调整 parent = (child - 1) / 2; } else {//该节点满足堆的特性,不需要进行调整 return; } } }
原理与向下调整算法相同。
4.堆的判空
int HeapEmpty(Heap* hp) {// 堆的判空 assert(hp); return hp->size == 0;//空返回1,非空返回0 }
5.堆的删除
void HeapErase(Heap* hp) {// 堆的删除(删除堆顶) assert(hp); if (HeapEmpty(hp)) {//判空 return; } Swap(&hp->array[0], &hp->array[hp->size - 1]);//先交换堆顶与堆尾元素 hp->size--;//堆内有效元素个数减一 AdjustDown_Heap(hp, 0);//堆顶向下调整 }
(1)交换堆顶与末尾元素;(2)删除末尾元素;(3)堆根结点进行向下调整。
6.取堆顶元素
HPDataType HeapTop(Heap* hp) {// 取堆顶的数据 assert(hp); return hp->array[0]; }
7.返回堆有效元素个数
int HeapSize(Heap* hp) {// 堆的数据个数 assert(hp); return hp->size; }
8.堆销毁
void HeapDestroy(Heap* hp) {// 堆的销毁 assert(hp); if (hp->array) { free(hp->array); hp->array = NULL; hp->capacity = 0; hp->size = 0; } }
(1)释放数组空间;(2)置零容量与有效元素个数。
四、接口测试
1.测试函数
void Test_Heap() { int array[] = { 49, 27, 37, 65, 28, 34, 25, 15, 18, 19 }; Heap hp; HeapCreate(&hp, array, sizeof(array) / sizeof(array[0]), Max);//创建小根堆 print_Heap(&hp); printf("top = %d\n", HeapTop(&hp)); printf("size = %d\n", HeapSize(&hp)); HeapPush(&hp, 10);//插入10 printf("top = %d\n", HeapTop(&hp)); printf("size = %d\n", HeapSize(&hp)); HeapErase(&hp); printf("top = %d\n", HeapTop(&hp)); printf("size = %d\n", HeapSize(&hp)); print_Heap(&hp); HeapDestroy(&hp);//销毁 }
2.测试结果
2.1建大堆:
2.2建小堆(实参Max改为Min)
五、完整代码
#include<stdio.h> #include<malloc.h> #include<assert.h> #include<string.h> typedef int HPDataType; typedef int (*PFUNC)(HPDataType left, HPDataType right); typedef struct Heap { HPDataType* array; int size;//有效元素个数 int capacity;//容量 PFUNC pCompare;//大根堆or小根堆 回调函数 }Heap; void HeapCreate(Heap* hp, HPDataType* array, int length, PFUNC pCompare);// 堆的构建 int Min(HPDataType right, HPDataType left);//创小根堆回调函数 int Max(HPDataType right, HPDataType left);//创大根堆回调函数 void AdjustDown_Heap(Heap* hp, int parent);//堆调整函数 void Swap(HPDataType* parent, HPDataType* child);//将结点与较大或较小的孩子进行交换 void HeapCheckCapacity(Heap* hp); //判满 & 扩容 int HeapEmpty(Heap* hp);// 堆的判空 void HeapPush(Heap* hp, HPDataType x);// 堆的插入 void HeapErase(Heap* hp);// 堆的删除 HPDataType HeapTop(Heap* hp);// 取堆顶的数据 int HeapSize(Heap* hp);// 堆的数据个数 void HeapDestroy(Heap* hp);// 堆的销毁 void print_Heap(Heap* hp);//打印函数 void Test_Heap();//测试函数 int main() { Test_Heap(); return 0; } void print_Heap(Heap* hp) {//打印函数 printf("-----------------------------\n"); for (int i = 0; i < hp->size; i++) { printf("%d ", hp->array[i]); } printf("\n-----------------------------\n"); } void Test_Heap() { int array[] = { 49, 27, 37, 65, 28, 34, 25, 15, 18, 19 }; Heap hp; HeapCreate(&hp, array, sizeof(array) / sizeof(array[0]), Min);//创建小根堆 print_Heap(&hp); printf("top = %d\n", HeapTop(&hp)); printf("size = %d\n", HeapSize(&hp)); HeapPush(&hp, 10);//插入10 printf("top = %d\n", HeapTop(&hp)); printf("size = %d\n", HeapSize(&hp)); HeapErase(&hp); printf("top = %d\n", HeapTop(&hp)); printf("size = %d\n", HeapSize(&hp)); print_Heap(&hp); HeapDestroy(&hp);//销毁 } void Swap(HPDataType* parent, HPDataType* child) {//将结点与较大或较小的孩子进行交换 HPDataType temp = *parent; *parent = *child; *child = temp; } int Min(HPDataType right, HPDataType left) {//创小根堆回调函数 return right < left; } int Max(HPDataType right, HPDataType left) {//创大根堆回调函数 return right > left; } void HeapCreate(Heap* hp, HPDataType* array, int length, PFUNC pCompare) {// 堆的构建 assert(hp); hp->array = (HPDataType*)malloc(sizeof(HPDataType) * length); if (hp->array == NULL) {//申请失败 assert(0); return; } hp->capacity = length; memcpy(hp->array, array, sizeof(HPDataType) * length);//将数组内元素拷贝到堆中 hp->size = length; hp->pCompare = pCompare; for (int root = ((hp->size - 1) - 1) / 2; root >= 0; root--) {//对堆进行调整 AdjustDown_Heap(hp, root);//堆调整函数 } } void AdjustDown_Heap(Heap* hp,int parent) {//堆向下调整函数 int child = parent * 2 + 1;//parent可能只有左没有右,所以标记左孩子 int size = hp->size; while (child < size) { if (child + 1 < size && hp->pCompare(hp->array[child + 1], hp->array[child])) {//找parent较小的孩子 child += 1; } //检测当前结点即孩子是否满足堆的特性 if (hp->pCompare(hp->array[child], hp->array[parent])) {//较小(较大)孩子比结点小(大),交换两个结点 Swap(&hp->array[parent], &hp->array[child]); parent = child;//交换后子树可能不满足小(大)堆,继续往下标记判断 child = parent * 2 + 1; } else {//当前结点满足堆特性,不需要继续向下调整 return; } } } void AdjustUp_Heap(Heap* hp, int child) {//向上调整函数 int parent = (child - 1) / 2;//标记该结点的双亲结点 while (child) { if (hp->pCompare(hp->array[child], hp->array[parent])) {//该结点比双亲结点小(大) Swap(&hp->array[parent], &hp->array[child]);//较小的往上交换 child = parent;//标记双亲结点为新的child结点,继续向上调整 parent = (child - 1) / 2; } else {//该节点满足堆的特性,不需要进行调整 return; } } } void HeapCheckCapacity(Heap* hp) {//判满&扩容 assert(hp); if (hp->size == hp->capacity) { int newCapacity = hp->capacity * 2;//扩大为原容量两倍 HPDataType* temp = (HPDataType*)malloc(sizeof(HPDataType) * newCapacity); if (temp == NULL) {//申请失败 assert(0); return; } memcpy(temp, hp->array, sizeof(HPDataType) * hp->size);//将数据拷贝到新空间中 free(hp->array); hp->array = temp; hp->capacity = newCapacity; } } void HeapPush(Heap* hp, HPDataType x) {// 堆的插入 assert(hp); HeapCheckCapacity(hp);//判满&自动扩容 hp->array[hp->size] = x;//插入到堆的末尾 hp->size++; AdjustUp_Heap(hp, hp->size - 1);//插入后需向上调整 } void HeapErase(Heap* hp) {// 堆的删除(删除堆顶) assert(hp); if (HeapEmpty(hp)) {//判空 return; } Swap(&hp->array[0], &hp->array[hp->size - 1]);//先交换堆顶与堆尾元素 hp->size--;//堆内有效元素个数减一 AdjustDown_Heap(hp, 0);//堆顶向下调整 } HPDataType HeapTop(Heap* hp) {// 取堆顶的数据 assert(hp); return hp->array[0]; } int HeapSize(Heap* hp) {// 堆的数据个数 assert(hp); return hp->size; } int HeapEmpty(Heap* hp) {// 堆的判空 assert(hp); return hp->size == 0;//空返回1,非空返回0 } void HeapDestroy(Heap* hp) {// 堆的销毁 assert(hp); if (hp->array) { free(hp->array); hp->array = NULL; hp->capacity = 0; hp->size = 0; } }