一.什么是堆?
字如其形,它是一种完全二叉树,就像这样:
不过应该反过来看,像个小山一样:
最上方的出发点叫做堆顶,分出两个枝干叫做子叶,完全二叉树。
堆中节点的值都大于等于(或小于等于)其子节点的值,堆中如果节点的值都大于等于其子节点的值,我们把它称为大顶堆,如果都小于等于其子节点的值,我们将其称为小顶堆。
二.向上调整算法和向下调整算法。
1.理论依据
由于堆只有左右子树所以不难通过计算找到它的“父亲”和“孩子”。
父子节点下标有个规律关系:
1.leftchild=parent*2+1 (奇数)
2rightchild=parent*2+2 (偶数)
int parent=(child-1)/2
通过这个我们可以轻松找到“父亲下标”。
2.代码实现
在创建堆时,我们加入size和capacity变量实时记录并扩容,用数组a储存数据
typedef struct Heap {
heapdatatype* a;
int size;
int capacity;
}Heap;
放入数据与顺序表一样,直接尾插,只不过在最后需要使用“向上调整算法”
void HeapPush(Heap* php, heapdatatype x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
heapdatatype* temp = (heapdatatype*)realloc(php->a, sizeof(heapdatatype) * newcapacity);
if (temp == NULL)
{
perror("realloc fail");
}
php->a = temp;
php->capacity = newcapacity;
}
php->a[php->size] = x;
php->size++;
Adjustup(php->a, php->size-1);
}
“向上调整算法”
思路是将数组最后加入的元素下标传入,通过反复寻找父亲然后比较,调整至合适的位置。
如图加入数据10:
经过调整后:
void Swap(heapdatatype* p1, heapdatatype* p2)
{
heapdatatype tem = *p1;
*p1 = *p2;
*p2 = tem;
}
void Adjustup(heapdatatype* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[child] <a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else break;
}
}
我们添加几个数据试试:
#include"Heap.h"
int main()
{
int arr[20] = { 99,78,43,23,14,29,77,44,0,88,22,18,43,55,1,2,3,4,78,5 };
Heap FirstHeap;
HeapInit(&FirstHeap);
for (int i = 0; i < 20; i++)
{
HeapPush(&FirstHeap, arr[i]);
}
for (int i = 0; i < 20; i++)
{
printf("%4d", FirstHeap.a[i]);
}
HeapDestroy(&FirstHeap);
return 0;
}
我们将数据用堆的形状表示看看:
完全符合小堆。
“向下调整算法”
如果需要删除数据则需要搭配向下调整算法。
为了保持小堆结构,我们采用将堆末与堆顶元素互换,从而删掉堆末,再通过向下调整算法恢复结构:
通过寻找孩子,比较大小,交换数据,完成调整:
void HeapPop(Heap* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, 0,php->size);
}
void AdjustDown(heapdatatype* a, int parent ,int size)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child] > a[child + 1]) child++;
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 +1;
}
else
{
break;
}
}
}
需要注意的是,我们将size传了进来,方便判断找到最后的孩子并跳出循环。
同样尝试一下:
删除了15个数据,仍然满足“小堆结构”
3.top K问题
TopK算法是一种用于找到数据集中最大或最小的K个元素的算法。
这里我们就可以借助堆排序完成,首先生成一个大小为K的小队顶。
接着,从第k+1个元素开始扫描,和堆顶(堆中最小的元素)比较,如果被扫描的元素大于堆顶,则替换堆顶的元素,并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。
这种解决方式大量节省了空间和时间。是不是很有趣呢,大家可以下来尝试一下。
感谢阅读,留个三联交个朋友。