目录
一、堆的性质
只要满足以下两点,它就是一个堆:
每个结点的值都大于或等于其左右孩子结点的值,我们叫做“大顶堆”。
每个结点的值都小于或等于其左右孩子结点的值,我们叫做“小顶堆”。
二、堆的相关基础操作
堆的创建
// 堆的构建
void HeaPCreate(Heap* hp, HPDataType* a, int n)
{
//申请一个和传入的数组一样大的空间保存堆
hp->arry = (HPDataType*)malloc(sizeof(HPDataType)*n);
if (NULL == hp->arry)
{
assert(hp);
return;
}
memcpy(hp->arry, a, n * 4);
hp->capacity = n;
hp->size = n;
for (int root = (n - 2) / 2; root >= 0; root--)
{
//用向下调整从下向上(从第一个非叶子节点开始)依次对堆中的节点进行调整
AjustdownHeap(hp,root);
}
}
堆的插入
只能在尾插入然后进行向上调整
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
//申请新空间
size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc failed\n");
exit(-1);
}
//把a指向新的地址
php->a = tmp;
//更新容量
php->capacity = newCapacity;
}
//将x放在堆尾
php->a[php->size] = x;
++php->size;
// 向上调整,控制保持是一个小堆
AdjustUp(php->a, php->size - 1);
}
void AdjustUp(HPDataType* a, size_t child)
{
//找到他的父亲
size_t parent = (child - 1) / 2;
//到堆顶了就不需要再进行调整
while (child > 0)
{
//如果孩子比父亲小,那就进行交换
if (a[child] < a[parent])
//if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void Swap(HPDataType* pa, HPDataType* pb)
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
堆的删除
只能删除堆顶的元素,其方法是将堆顶的元素和堆尾进行交换,删除堆尾的元素,然后将堆顶元素进行向下调整
// 删除堆顶的数据。(最小/最大)
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);
}
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
size_t parent = root;
//默认是左孩子
size_t child = parent * 2 + 1;
//走到堆尾就结束,不用再进行调整
while (child < size)
{
// 1、选出左右孩子中小的那个
if (child + 1 < size && a[child + 1] < a[child])
{
++child;
}
// 2、如果孩子小于父亲,则交换,并继续往下调整
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
获取堆顶元素
HPDataType HeapTop(HP* php)
{
assert(php);
//数组长度大于0 才能运行,否则非法访问
assert(php->size > 0);
return php->a[0];
}
堆的判空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
堆的有效元素个数
size_t HeapSize(HP* php)
{
assert(php);
return php->size;
}
堆的销毁
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
三、堆排序
建堆
升序:建大堆(不能建小堆)
降序:建小堆
我们可以直接对数组建堆,使数组满足堆的性质
例如:
int main()
{
int a[] = { 4, 2, 7, 8, 5, 1, 0, 6 };
HeapSort(a,sizeof(a)/sizeof(a[0]));
for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
void HeapSort(int* a, int n)
{
assert(a);
//我们可以把数组看成依次插入n个数
//每次对他们进行向上调整就可以形成一个堆
/*for(int i = 1;i<n;i++)
{
AdjustUp(a, i);
}*/
//向下调整
//找到最后一个数的父亲,然后进行向下调整
//依次往上走,直到走到根节点
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
size_t end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
2.top-k问题:
TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大 。
比如:专业前 10 名、世界 500 强、富豪榜、游戏中前 100 的活跃玩家等。
对于 Top-K 问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了 ( 可能数据都不能一下子全部加载到内存中 ) 。最佳的方式就是用堆来解决,
基本思路如下:
点个赞吧!