堆本质上是一个完全二叉树,但存储结构是一个数组。这个是对堆进行代码编写的核心,要牢牢把握住!
堆的物理本质是一个数组,就可以像动态顺序表一样进行结构定义。
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size; // 有效元素个数
int capacity; // 数组长度
}HP;
// 堆的初始化
void HeapInit(HP* hp);
// 堆的销毁
void HeapDestroy(HP* hp);
// 堆push数据
void HeapPush(HP* hp, HPDataType x);
// 堆pop堆顶
void HeapPop(HP* hp);
// 获得堆顶数据
HPDataType HeapTop(HP* hp);
// 空堆
bool HeapEmpty(HP* hp);
// 堆大小
int HeapSize(HP* hp);
void HeapInit(HP* hp)
{
assert(hp);
hp->a = NULL;
hp->size = hp->capacity = 0;
}
顺序表空间连续,所以只要free(首地址)就可以。
注意:不能忘记 hp->capacity = hp->size = 0;
void HeapDestroy(HP* hp)
{
assert(hp);
free(hp->a);
hp->a = NULL;
hp->capacity = hp->size = 0;
}
因为堆存储结构就是一个数组,所以我们在数组末入数据,然后再进行向上调整算法。
void HeapPush(HP* ph, HPDataType x)
{
assert(ph);
// 判断扩容
if (ph->size == ph->capacity)
{
// 扩容
size_t newcapacity = ph->capacity == 0 ? 4 : 2 * ph->capacity;
HPDataType* tmp = realloc(ph->a, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
// 扩容失败
printf("realloc fail\n");
exit(-1);
}
ph->capacity = newcapacity;
ph->a = tmp;
}
// 数组尾插数据
ph->a[ph->size++] = x;
// 向上调整
// 参数: 堆数组,插入位置下标
AdjustUp(ph->a, ph->size - 1);
}
对插入数据大小不同,调整的最终条件也不同。
调整
继续调整
结束!
代码:
// 向上调整算法 此处以大堆为例
void AdjustUp(int* a, int child)
{
assert(a);
int parent = (child - 1) / 2;
// 结束条件如果是parent>=0,会进入到下一个循环通过break跳出
while (child > 0)
{
if (a[parent] < a[child])
{
// 父亲结点值小于孩子结点
Swap(&a[parent], &a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
堆顶pop分为三个主要步骤:首先堆顶和数组最后一个元素交换、数组个数减一、再进行向下调整算法。
void HeapPop(HP* hp)
{
assert(hp);
assert(hp->size != 0);
// 交换数组首尾元素
Swap(&hp->a[0], &hp->a[hp->size - 1]);
// 数组size减一
hp->size--;
// 堆顶元素向下调整
AdjustDown(hp->a, hp->size, 0);
}
调整结束的条件:
右孩子更小,小孩子小于父亲,调整
孩子大于父亲,结束调整。
// 向下调整算法,这里默认是调整成小堆
void AdjustDown(int* a, int n, int parent)
{
assert(a);
int child = parent * 2 + 1; // 默认是左孩子
while (child < n)
{
// 找出小孩子
if (child + 1 < n && a[child + 1] < a[child])
{
++child;
}
// 如果小孩子比父亲小,则交换,继续调整
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
进行空堆判断。
HPDataType HeapTop(HP* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
return hp->a[0];
}
bool HeapEmpty(HP* hp)
{
assert(hp);
return hp->size == 0;
}
int HeapSize(HP* hp)
{
assert(hp);
return hp->size;
}
本文的重点在于堆向下和向上调整。
堆的应用 比如 TopK问题以及堆排序会在后面继续更新。
码文不易,欢迎三连,深鞠躬!