二叉树顺序存储的应用之堆
1,堆的概念
将元素集合按照二叉树顺序存储的方式存储到数组中,满足任意节点比其孩子节点大或小。
2,堆的性质
- 堆顶元素一定是最小的或者最大
- 堆中每个元素都大于或小于其孩子节点
- 堆是一颗完全二叉树
3,堆的基本操作
以建立小堆为例:
typedef int HDataType;
typedef struct Heap
{
HDataType* array;
int capacity;
int size;
}Heap;
(1)建堆操作
- 根据元素个数初始化堆空间
- 将数组中的元素拷贝到堆结构(完全二叉树,还不属于堆,需要进行调整,让其满足堆的性质)中的连续空间中
- 对堆结构中的数据进行调整,让其满足堆的性质(往下调整)
//初始化一个空堆
void HeapInit(Heap* hp,int initCap)
{
assert(hp);
initCap = initCap < 10 ? 10 : initCap;
hp->array = (HDataType*)malloc(sizeof(HDataType) * initCap);
if (NULL == hp->array)
{
assert(0);
return;
}
hp->capacity = initCap;
hp->size = 0;
}
//用数组中的元素创建堆
void HeapCreate(Heap* hp, int* array, int size)
{
//1,先将数组中的元素放到堆结构中
HeapInit(hp, size);
memcpy(hp->array, array, sizeof(HDataType)*size);
hp->size = size;
//2,进行调整
for (int root = (size - 2) / 2; root >= 0; root--)
{
AdjustDown(hp->array, hp->size, root);
}
}
往下调整操作(要求在调整某个节点的时候要求该节点的左右子树已经满足堆的性质)
- 找完全二叉树中倒数第一个非叶子节点(刚好是最后一个节点的双亲)也就是下标为((size-1)-1)/2的节点
- 从((size-1)-1)/2节点位置一直往前调整直到根节点的位置,对上面的每一个非叶子节点进行向下调整
void AdjustDown(HDataType* array, int size, int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size&&array[child + 1] < array[child])
child += 1;
//parent较小的孩子已经找到
//检测parent是否满足堆的性质
if (array[child] < array[parent])
{
Swap(&array[child], &array[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
return;
}
}
}
(2)删除堆顶元素
- 检测堆是否为空,堆为空直接返回
- 堆不为空,将堆顶元素与堆中最后一个元素进行交换,将堆中的有效元素个数减1,对堆顶元素进行向下调整。
void HeapPop(Heap* hp)
{
if (HeapEmpty(hp))
return;
Swap(&hp->array[0], &hp->array[hp->size - 1]);
hp->size--;
AdjustDown(hp->array, hp->size, 0);
}
(3)堆的插入
void HeapPush(Heap* hp, HDataType data)
{
//检测是否需要扩容
CheckCapcity(hp);
//1,将新元素插入到数组的末尾,即将新元素插入到完全二叉树的末尾
hp->array[hp->size++] = data;
//2,新元素的插入,可能会破坏堆的性质
AdjustUp(hp->array, hp->size, hp->size - 1);
}
向上调整操作:
void AdjustUp(HDataType* array, int size, int child)
{
int parent = ((child - 1) / 2);
while (child)
{
if (array[child] < array[parent])
{
Swap(&array[child], &array[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
return;
}
}
检查扩容操作:
void CheckCapcity(Heap* hp)
{
assert(hp);
if (hp->size == hp->capacity)
{
//1,开辟新空间
int newCapacity = hp->capacity * 2;
HDataType* temp = (HDataType*)malloc(sizeof(HDataType)*newCapacity);
if (NULL == temp)
{
assert(0);
return;
}
//2,拷贝元素
memcpy(temp, hp->array, hp->size*sizeof(HDataType));
//3,释放就空间
free(hp->array);
//4,使用新空间
hp->array = temp;
hp->capacity = newCapacity;
}
}
4,用堆解决top k问题
假设:100亿个整形元素,要求100亿个元素中最大的前k个。
解决方法:
- 用数据中的前k个元素创建一个小堆
- 用剩余元素(N-K)依次与堆顶元素进行比较,如果比堆顶元素大,将堆顶元素删除掉,将新元素插入到堆中即可