要想了解堆结构,首先要知道什么是堆、堆是用来做什么的。
那么什么是堆呢?
如果有一个关键码的集合K,K中包含n个数据,将这些元素按照完全二叉树的顺序存储方式存储在一个一维数组中,并满足第i个数据小于等于第2*i+1个数据且第i个数据小于等于第2*i+2个数据(i为数组下标),则称其为小堆(大堆:第i个数据大于等于第2*i+1个数据且第i个数据大于等于第2*i+2个数据)。将根节点最大的堆叫做大根堆或最大堆,将根节点最小的堆叫做小根堆或最小堆。
堆的性质:
1、堆中某个节点的值总是不大于或不小于其父节点的值;
2、堆总是一颗完全二叉树。
如何实现堆:
既然堆是由数组来实现的那么我们来提供一组数据,默认这组数据是一颗完全二叉树。
cpp
int array[] = {27,15,19,18,28,34,65,49,25,37};
接下来我们利用这组数据使用向下调整算法来创建堆。
void HeapSort(int* arr, int sz)
{
//首先使用前几个数据进行建堆
//对于一个包含 sz 个元素的数组,其最后一个非叶子节点的下标计算方式为 (sz - 2) / 2
//(叶子节点本身就是一个完整的堆,所以不需要再进行调整)
for (int i = (sz - 2) / 2; i >= 0; i--)
{
//向下调整
AdjustDown(arr, i, sz);
}
//使堆顶数据和最后的数据进行交换
//并继续进行向下调整算法
int end = sz - 1;
while (end > 0)
{
Swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}
}
void AdjustDown(int* arr, int parent, int sz)
{
//左子树
int child = parent * 2 + 1;
while (child < sz)
{
//先判断左子树和右子树的大小
//建小堆取大值,建大堆取小值
if (child + 1 < sz && arr[child] > arr[child + 1])
{
child++;
}
//判断子树和父节点谁大谁小
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
上面的代码是以建大根堆的形式展现的,如果需要建小根堆则调整判定条件即可
调整1:将左右孩子节点中的选择改为让较大值和父节点比较
调整2:如果孩子节点大于父节点再进行交换
建堆分为两种方法,一种是向上调整算法,一种是向下调整算法,向上调整一般用于数据的插入,向下调整一般用与数据的删除!
上面的是向下调整算法,下面是向上调整算法
void AdjustUp(int* arr, int child)
{
int parent = (child - 1) / 2;
//不需要等于,child只要走到根节点的位置,根节点没有父节点不需要交换
while (child > 0)
{
//建大堆,>
//建小堆,<
if (arr[child] < arr[parent])
{
Swap(&arr[parent], &arr[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
堆的作用
1.可以用于排序算法中的一项
2.可以解决一些用于处理大量数据中的前几项(最大的几项或者最小的几项)比如TOP-K问题
TOP-K问题
即获取一段数据中的前K个最大值或者最小值
首先来创建一段数据
void CreateNDate()
{
// 造数据
int n = 10000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (size_t i = 0; i < n; ++i)
{
int x = rand() % 1000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
然后使用前K个数据建堆
//使用前K个数据造堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
//找前K个最大的数据使用小堆
AdjestDown(arr, 0, n - 1);
}
接下来遍历数据中的每一个值,将大于堆顶的数据进堆并进行排序
//定义一个变量用于接收文件中的值
int x = 0;
while (fscanf(fout, "%d", &x) != EOF)
{
//将读取到的数据跟堆顶的数据进行比较
//读取到的数据大于堆顶的数据,进行交换
if (x > arr[0])
{
arr[0] = x;
AdjestDown(arr, 0, n);
}
}
此时我们就得到了所有数据中最大的前K个