- 堆表面是数组,内核是完全二叉树/满二叉树
- 在插入删除的时候要注意操作过后堆是否还是一个堆,要进行交换等操作。(向上调整)
- 逻辑上控制二叉树,物理上控制数组!!!
接下来我们用【小堆】来介绍堆的代码实现。
Heap.h
#pragma once#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int HPDataType;//定义堆结构体
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;void HeapInit(HP* php);
void HeapDestroy(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
HPDataType HeapTop(HP* php);
int HeapSize(HP* php);
bool HeapEmpty(HP* php);
Heap.c
HeapInit初始化
void HeapInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}
HeapDestroy销毁
void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}
HeapPush插入数据
- 这里要注意在一个堆后插入一个数,这个堆是否还是堆的问题。所以要将插入的数和他的父亲进行比较。
- 同时要考虑,如何使一个随机的数组排列成堆?这个问题在test.c中就可解决。
Swap函数实现
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
AdjustUp函数实现
- 理解判断条件:child > 0
- 首先比较插入的数和它的父亲,然后进行判断。
- 若满足判断条件,就交换两个数的位置,并让child向上调整到parent的位置,让parent向上调整到它的父亲的位置,依此类推。
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;//while (parent >= 0) 不能这样写,因为parent不可能<0while (child > 0) //用child判断{if (a[child] < a[parent]){Swap(&a[child], &a[parent]);//往上走child = parent;parent = (child - 1) / 2;//child = (child - 1) / 2;//parent = (parent - 1) / 2;}else{break;}}
}
HeapPush函数实现
- 时间复杂度为O(logN),也就是高度次。
void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newCapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}
HeapPop删除数据
- 删除根
- 不能用挪动覆盖法,堆会彻底乱掉,且时间复杂度是O(N)。
- 方法:首尾交换再尾删,然后向下调整。 这样左右子树依旧是小堆。时间复杂度为O(logN)。
- 如何判断叶子? 叶子*2+1>size
Swap函数实现
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
AdjustDown函数实现
- 越界问题:这里要注意在while里一定要加上【child+1<size】这个判断条件,因为如果child是完全二叉树叶子的左孩子,而且没有右孩子的情况,那么【if (a[child + 1] < a[child])】这个child+1就会越界。
void AdjustDown(int* a, int size, int parent)
{//假设左孩子小,如果事实是右孩子小,那么就更新childint child = parent * 2 + 1;while (child+1 < size && child < size){if (a[child + 1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
HeapPop函数实现
void HeapPop(HP* php)
{assert(php);assert(php->size > 0);//首尾交换Swap(&php->a[0], &php->a[php->size - 1]);//删除php->size--;//向下调整AdjustDown(php->a, php->size, 0);
}
HeapTop取堆顶元素
HPDataType HeapTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}
HeapSize堆元素个数
int HeapSize(HP* php)
{assert(php);return php->size;
}
HeapEmpty判断是否为空
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}
Test.c
首先测试一下插入。
- ⭐要注意这里使用了for循环,将数组中的数一个一个插入,然后排列成堆。这样就能保证这个数组是一个堆啦!
int main()
{int a[] = { 4,6,2,1,5,8,2,9};HP hp;HeapInit(&hp);for (int i = 0; i < sizeof(a) / sizeof(int); ++i){HeapPush(&hp, a[i]);}while (!HeapEmpty(&hp)){printf("%d ", HeapTop(&hp));HeapPop(&hp);}printf("\n");return 0;
}
测试取数组中前k个小的元素。
- 这里先将数组里的数据排列成一个小堆,然后用while循环, 取一个堆顶元素删一个。
int main()
{int a[] = { 4,6,2,1,5,8,2,9};HP hp;HeapInit(&hp);for (int i = 0; i < sizeof(a) / sizeof(int); ++i){HeapPush(&hp, a[i]);}int k = 3;while (k--){printf("%d\n", HeapTop(&hp));HeapPop(&hp);}return 0;
}
输出小堆
int main()
{int a[] = { 4,6,2,1,5,8,2,9};HP hp;HeapInit(&hp);for (int i = 0; i < sizeof(a) / sizeof(int); ++i){HeapPush(&hp, a[i]);}while (!HeapEmpty(&hp)){printf("%d ", HeapTop(&hp));HeapPop(&hp);}printf("\n");return 0;
}