1.基本概念及性质
- 概念:如果有一个关键码的集合K = (k0, k1, k2, …, kn - 1),把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i + 1且Ki <= K2i + 2(i = 0, 1, 2, …),则称为小堆,根节点最小的堆叫做最小堆或者小根堆。若所有元素满足:Ki >= K2i + 1且Ki >= K2i + 2(i = 0, 1, 2, …),则称为大堆,根节点最大的堆叫做最大堆或者大根堆。
- 性质:① 堆中某个节点的值总是不大于或者不小于其父节点的值。 ② 堆总是一棵完全二叉树。
- 大小根堆的示意图:
2.堆的实现
(1.)堆的结构
// 堆的结构
#define int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
void HeapInit(Heap* hp, HPDataType* a, int n);// 堆的构建
void HeapDestory(Heap* hp);// 堆的销毁
void HeapPush(Heap* hp, HPDataType x);// 堆的插入
void HeapPop(Heap* hp);// 堆的删除
HPDataType HeapTop(Heap* hp);// 获取堆的根节点
int HeapSize(Heap* hp);// 获取堆的大小
bool HeapEmpty(Heap* hp);// 判断堆是否为空
void HeapSort(HPDataType* a, int n);// 堆排序
(2.)堆向下调整算法
-
通过从根节点开始的向下调整算法可以把一棵完全二叉树调整成一个小堆或者大堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
-
int arr[] = {27, 15, 19, 18, 28, 34, 65, 49, 25, 37};以建一个小堆为例:
-
实现代码
// 堆的向下调整算法实现代码
void Swap(int* p, int* q)
{
int* tmp = p;
p = q;
q = tmp;
}
void AdjustDown(HPDataType* a, size_t n, size_t parent)
{
size_t child = parent * 2 + 1;
while(child < n)
{
// 获取比较值比较小的孩子
if(child + 1 < n && a[child + 1] < a[child])
++child;
// 如果父节点值大于孩子节点中值较小者,
// 将父节点与孩子节点中值较小者交换
if(a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
(3.)堆的构建
-
构建堆是从倒数第一个非叶子节点的子树开始调整,一直调整到根节点的树,就就可以调整成堆。
-
int arr[] = {9, 8, 7, 2, 6, 4};以构建小根堆为例:
-
实现代码
-
最后一个叶子节点,即最后一个节点的父节点。其下标为:(n - 1 - 1) / 2。
// 构建堆的实现代码
void HeapInit(Heap* hp, HPDataType* a, int n)
{
hp->_a = malloc(sizeof(HPDataType) * n);
memcpy(hp->_a, a, sizeof(HPDataType) * n);
hp->_size = n;
hp->_capacity = n;
// 构建成堆
for(size_t i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(hp->_a, hp->_size, i);
}
}
(5.)堆的向上调整算法以及堆的插入
- 先将要插入的值插入到数组的尾上,再进行向上调整算法,直到满足堆。
- 以上面为例,插入数据0,且依旧是一个小根堆。示意图如下:
- 堆的向上调整算法实现代码
// 堆的向上调整算法实现代码
void Swap(int* p, int* q)
{
int* tmp = p;
p = q;
q = tmp;
}
void AdjustUp(HPDataType* a, size_t child)
{
size_t 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;
}
}
}
- 堆的插入的实现代码
// 堆的插入的实现代码
void HeapPush(Heap* hp, HPDataType)
{
if(hp->_size == hp->_capacity)
{
size_t newcapacity = hp->_capacity == 0 ? 2 : hp->_capacity * 2;
hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
hp->_capacity = newcapacity;
}
hp->_a[hp->_size] = x;
++hp->_size;
AdjustUp(hp->_a, hp->_size - 1);
}
(6.)堆的删除
-
删除堆是删除堆顶的数据,将堆顶的数据和最后 一个数据一交换,然后删除最后一个数据,最后再进行向下调整算法。
-
比如,上面的如果要删除0,示意图如下:
-
实现代码
// 堆的删除的实现代码
void Swap(int* p, int* q)
{
int* tmp = p;
p = q;
q = tmp;
}
void HeapPop(Heap* hp)
{
assert(hp->_size > 0);
Swap(&hp->_a[hp->_size - 1], &hp->_a[0]);
--hp->_size;
AdjustDown(hp->_a, hp->_size, 0);
}
(7.)堆排序
- 升序就是建大堆,要是降序就是建小堆。
- 详情见博客数据结构之八种排序方式。
(8.)实现堆的其它接口的代码。
void HeapDestory(Heap* hp)
{
// 堆的销毁
if(hp->_a)
{
free(hp->_a);
hp->_a = NULL;
}
hp->_size = hp->_capacity = 0;
}
HPDataType HeapTop(Heap* hp)
{
// 获取堆的根节点
assert(hp->_size > 0);
return hp->_a[0];
}
int HeapSize(Heap* hp)
{
// 获取堆的大小
return hp->_size;
}
bool HeapEmpty(Heap* hp)
{
// 判断堆是否为空
return hp->_size == 0;
}