堆是用完全二叉树来实现,而它的节点是一层一层连在一起,所以我们可以用数组来实现他,将完全二叉树的节点按照堆的性质通过算法组合就实现了堆
堆的结构体以及函数声明
#pragma once
#include<stdio.h>
#include<windows.h>
#include<stdlib.h>
#include<assert.h>
//实现一个大堆
typedef int HpDataType;
typedef struct Heap
{
HpDataType* _a;
int _size;
int _capacity;
}Heap;
//堆排序
void HeapSort(int *a, int n);
//向下调整算法
void AdjustDown(HpDataType* a, int n, int root);
//向上调整算法
void AdjustUp(HpDataType* a, int n, HpDataType x);
//有些需求,上来就需要把数组变成堆。
void HeapInit(Heap* php, HpDataType *a,int n);
//打印
void HeapPrint(Heap* hp);
//摧毁
void Destory(Heap* php);
//数组尾插入
void HeapPush(Heap* php, HpDataType x);
//删除堆顶数据,删数组尾没意义
void HeapPop(Heap* php);
//堆顶
HpDataType HeapTop(Heap* php);
// 堆的数据个数
int Heapsize(Heap* php);
// 堆的判空
int HeapEmpty(Heap* php);
//TopK问题
//最大的前K个,建立小堆
void PrintMaxTopK(HpDataType* a, int n, int k);
堆的初始化
在主函数里声明堆变量,初始化直接写一个函数,因为我们的堆肯定是要动态增长的,用栈里的数组空间不够,也不能动态增长
//堆的初始化
void HeapInit(Heap* php, HpDataType *a, int n)
{
php->_a = (HpDataType*)malloc(sizeof(HpDataType)*n);
memcpy(php->_a, a, sizeof(HpDataType)*n);
php->_size = n;
php->_capacity = n;
//构建堆
//但是直接调用AdjustDown函数能完成吗?不能。因为它传进来数组,并没能满足左右子树都是小堆
//从最后一个节点的父亲开始调整,最后一个节点下标为n-1,父亲的坐标(n-1-1)/2
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(php->_a, php->_size, i);
}
}
向下调整算法
小堆为例
void AdjustDown(HpDataType* a,int n,int root)
{
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
//默认两个孩子中左孩子最小
//如果有右孩子小于左孩子,child++,变成右孩子
//因为后面要与两个孩子中最小的一个进行交换,比最小的小,也就比另一个孩子小
//这里child+1小于n,是因为一种情况只有左孩子,而if语句里面+1数组越界。
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;
}
}
}
向上调整算法
push插入数据时需要向上调整保证队的性质不会发生改变
void AdjustUp(HpDataType* a, int n, int child)
{
int parent = (child - 1) / 2;
//由于它是分数计算出来的所以永远不会小于0
/*while (parent >= 0)*/
while (child>0)
{
if (a[child]<a[parent])
{
Swap(&a[child], &a[parent]);
//下标,孩子向上走
child = parent;
//你也继续上去,下次循环继续比较
parent = (child - 1) / 2;
}
else
{
//说明不需要上去了
break;
}
}
}
堆排序
void HeapSort(int *a, int n)
{
//要进行堆排序,先把数组变成堆
//从最后一个非叶子节点开始,叶子节点没必要向下调整
//外面初始化已经建好堆了
/*for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a,n,i);
}
*/
//这个代码虽然也可以,但是不好看,也不巧妙
/*int count = n;
for (int i = count; i >0; i--)
{
int end = n - 1;
Swap(&a[0],&a[end]);
n--;
AdjustDown(a, n, 0);
}*/
int end = n - 1;
//小于等于0结束,大于0继续
while (end > 0)
{
Swap(&a[0], &a[end]);
//堆的大小每次减一,每次从堆顶开始。(因为除了根节点都是小堆可以直接使用向下调整)
AdjustDown(a, end, 0);
end--;
}
}
TopK问题
寻找最大或者最小的K个数。
这里以寻找最大的K个数为例:
建立小堆
//建立小堆,与堆顶的数据比较,比他大就把他换了
void PrintMaxTopK(HpDataType* a, int n, int k)
{
Heap hp;
//建立K个元素的小堆
HeapInit(&hp, a, k);
for (int i = k; i < n; i++)
{
if (a[i]>hp._a[0])
{
//最开始把HpDataType ret = HeapTop(&hp);放在了这里,找了半天,实际原因很简单,每次变的是ret,而不是实际堆顶的数据
//放在这里每次都初始化一次,自然就出错了
hp._a[0] = a[i];
AdjustDown(hp._a, k, 0);
}
}
for (int i = 0; i < k; i++)
printf("%d ", hp._a[i]);
}