前言
各位读者朋友们大家好!这篇博客我们来讲一下数据结构的堆和堆排序以及代码的实现。
目录
一. 堆的概念
堆是一种满足特定条件的完全二叉树,主要可分为两种类型:大顶堆和小顶堆。
- 大顶堆: 父亲节点的值 >= 子节点的值。
- 小顶堆: 父亲节点的值 <= 子节点的值。
堆还有以下性质:
- 堆中某个结点的值总是不大于或不小于其父结点的值
- 堆总是一棵完全二叉树
二. 堆的实现
这里我们建大堆,如果要建小堆只需要调整修改大小逻辑判断(例如将<改为>)。
2.1 堆的存储与表示
因为堆总是一棵完全二叉树,所以我们用数组来存储堆会更加方便。当我们使用数组来实现堆的时候,很容易通过下标来找到某个节点的父亲节点和子节点。
- 父亲节点:(i - 1) / 2
- 子节点:2i + 1(左孩子节点)2i + 2(右孩子节点)
2.2 元素入堆
给定元素 val ,我们首先将其添加到堆底。添加之后,由于 val 可能大于堆中其他元素,堆的成立条件可能已被破坏,因此需要修复从插入节点到根节点的路径上的各个节点。
在对堆进行修复的时候,我们需要用到向上调整算法,即将比父亲节点大的节点的元素换到父亲节点的位置,父亲节点的元素跟该子节点交换。向上调整算法要求调整数据位置之前的数据是一个堆。
向上调整算法
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
2.3 删除堆顶元素
堆顶元素是二叉树的根节点,即数组首元素。如果我们直接从数组中删除首元素,那么堆的所有节点的索引都会发生变化,这将使得后续进行修复变得困难。为了尽量减少元素索引的变动,我们将堆顶元素与最后一个元素进行交换,然后删除最后一个元素,再进行堆的调整。
这里需要使用向下调整算法,我们在删除堆顶数据的时候,并没有破坏左子树和右子树的堆的属性,因此我们在向下调整时,只需比较根节点的值与左右子节点的值,如果小于子节点的值只需与子节点中较大的一个进行交换,直到调整到叶子节点即可。
向下调整算法
//parent是父亲节点的下标,n是数组的元素个数
void AdjustDown(HPDataType* a, int parent, int n)
{
int child = 2 * parent + 1;
while (child < n)//child>=n就说明到叶子节点了
{
//找左右孩子中较大的一个
if (child + 1 < n && a[child] < a[child + 1])//防止越界
{