一、堆的简介
堆通常是一个可以被看做一棵完全二叉树的数组对象。
堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
二、堆的结构
堆是一个完全二叉树的数组对象,所以堆的结构体中包含了数组指针,数组大小和数组容量。
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a; // 数组指针
HPDataType _size; // 数组大小
HPDataType _capacity; // 数组容量
}Heap;
三、堆的函数接口
void Swap(HPDataType* a, HPDataType* b);// 交换
void SmallAdjustDown(HPDataType* a, size_t n, size_t parent);// 小堆向下调整
void BigAdjustDown(HPDataType* a, size_t n, size_t parent);// 大堆向下调整
void HeapInit(Heap* hp, HPDataType* a, int n);// 初始化
void HeapDestory(Heap* hp);// 销毁
void SmallAdjustup(Heap* hp, HPDataType x);// 小堆向上调整
void BigAdjustup(Heap* hp, HPDataType x);// 大堆向上调整
void HeapPush(Heap* hp, HPDataType x);// 插入
void HeapPop(Heap* hp); // 删除
HPDataType HeapTop(Heap* hp); // 取堆顶元素
int HeapSize(Heap* hp); // 堆大小
int HeapEmpty(Heap* hp); // 堆是否为空
void HeapSort(HPDataType* a, size_t n);// 堆排序
void HeapPrint(Heap* hp);// 打印堆
四、函数接口实现
4.1 Swap交换函数
Swap函数按照传地址的方式,直接使用第三方交换指针指向的内容。
void Swap(HPDataType* a, HPDataType* b)// 交换
{
HPDataType c;
c = *a;
*a = *b;
*b = c;
}
4.2 小堆向下调整
我们把已经创建好的数组从根节点开始向下调整成一个小堆,因为要调成一个小堆,所以前提是该二叉树里面,左右子树必须是一个堆,才能调整。小堆就是父结点小于孩子结点,建立循环遍历,调整孩子结点和父结点位置即可。
void SmallAdjustDown(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[child] < a[parent])
{
Swap(&(a[child]),&(a[parent]));
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
4.3 大堆向上调整
和小堆向下调整类似,大堆是父结点大于孩子结点。
void BigAdjustDown(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[child] > a[parent])
{
Swap(&(a[child]), &(a[parent]));
parent = child;
child = parent * 2 + 1;
}
else break;
}
}
4.4 初始化
在初始化的时候,我们把堆结构体的成员赋初值,并且构建出一个小(大)堆。
void HeapInit(Heap* hp, HPDataType* a, int n)// 初始化
{
hp->_a = (HPDataType*)malloc(sizeof(HPDataType)*n);
memcpy(hp->_a, a, sizeof(HPDataType)*n);
hp->_capacity = n;
hp->_size = n;
//构建小堆
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
SmallAdjustDown(hp->_a, hp->_size, i);
}
// 构建大堆
/*for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
BigAdjustDown(hp->_a, hp->_size, i);
}*/
}
4.5 销毁
释放掉堆所占的数组空间,把堆结构体中的成员置空置0。
void HeapDestory(Heap* hp)// 销毁
{
assert(hp);
if (hp->_a)
{
free(hp->_a);
hp->_a = NULL;
hp->_capacity = 0;
hp->_size = 0;
}
}
4.6 小堆向上调整
小堆满足父结点小于孩子结点,把不满足要求的结点位置进行调整。
void SmallAdjustup(Heap* hp, HPDataType child)// 小堆向上调整
{
HPDataType parent = (child - 1) / 2;
while (child != 0)
{
if (hp->_a[child] < hp->_a[parent])
{
Swap(&(hp->_a[child]), &(hp->_a[parent]));
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
4.7 大堆向上调整
大堆满足父结点大于孩子结点,把不满足要求的结点位置进行调整。
void BigAdjustup(Heap* hp, HPDataType child)// 大堆向上调整
{
HPDataType parent = (child - 1) / 2;
while (child != 0)
{
if (hp->_a[child] > hp->_a[parent])
{
Swap(&(hp->_a[child]), &(hp->_a[parent]));
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
4.8 插入
在堆尾插入一个元素,再调用向上调整算法。
void HeapPush(Heap* hp, HPDataType x)// 插入
{
hp->_a[hp->_size] = x;
SmallAdjustup(hp, hp->_size);
//BigAdjustup(hp, hp->_size);
hp->_size++;
}
4.9 删除
删除堆是删除堆顶元素,把堆顶元素和最后一个元素一换,再删除数组最后一个元素,进行向下调整算法。
void HeapPop(Heap* hp) // 删除
{
Swap(&(hp->_a[0]), &(hp->_a[hp->_size - 1]));
hp->_size--;
SmallAdjustDown(hp, hp->_size,hp->_a[0]);
}
4.10 取堆顶元素
直接利用数组下标返回。
HPDataType HeapTop(Heap* hp) // 取堆顶元素
{
return hp->_a[0];
}
4.11 堆大小
堆的大小就是数组大小。
int HeapSize(Heap* hp) // 堆大小
{
return hp->_size;
}
4.12 堆是否为空
如果数组大小是0,则堆为空。
int HeapEmpty(Heap* hp) // 堆是否为空
{
if (hp->_size == 0)
{
return 1;
}
else return 0;
}
4.13 堆排序
升序建大堆,降序建小堆。
void HeapSort(int* a, size_t n)// 堆排序
{
for (int i = (n - 2) / 2; i >= 0; --i)
{
SmallAdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[end], &a[0]);
SmallAdjustDown(a, end, 0);
--end;
}
}