文章目录
- 一、堆的概念
- 二、堆的性质
- 三、堆的分类
- 1.大根堆
- 2.小根堆
- 四、说明
- 五、堆的结构
- 六、堆的向上调整
- 1.图示
- 2.代码实现
- ⌚️3.时间复杂度分析
- 七、堆的向下调整
- 1.思路:
- 2.代码实现
- 八、删除根
- 1.思路:
- 2.代码实现
- 九、创建小堆
- 1.思路:
- 2.代码实现
- 十、所有方法实现汇总
堆(Heap)
是计算机科学中一类特殊的数据结构的统称。如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1, 2…,则称为小堆
(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。除了最后一层以外上面的节点但是非空的,最后一层节点是从左到右依次排布的)
非线性,完全二叉树。适合用数组存储。
堆是无序的,也就是左右可以互换
最值总在 0 号位
根据这个特点我们就可以做很多事情,比如TopK问题 (在一堆数据里面找到前 K 个最大 / 最小的数).
比如点餐软件中有上千家店铺,我想选出该地区好评最多的十家川菜店,我们不用对所有数据排序,只需要取出前 K 个最大 / 最小数据。使用堆排序效率也更高。
1.大根堆 2.小根堆
定义:树中的任意一个双亲节点都小于等于孩子节点。
以下的方法均以小堆来推理,如果想实现大堆,则修改【>】符号等方式实现。
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
向上调整的前提是,调整位置之前必须是堆。
//向上调整
void AdjustUp(HPDataType* a, int child)
{
//传入数组,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
return;
}
}
时间复杂度:N*logN
最坏情况:调整到根;
最好情况:不用调整,
向下调整的前提是,左右子树必须是小堆或者大堆。
//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
//一直交换到数的最后,也就是数组的最后一个位置
while (parent<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
{
return;
}
}
}
先将根与最后一个节点交换,删除最后一个节点,在进行向下调整。
void HeapPop(HP* p)
{
assert(p);
assert(p->size > 0);
Swap(&p->a[0], &p->a[p->size - 1]);
--p->size;
AdjustDown(p->a, p->size, 0);
}
由于我的AdjustUp函数是用来调整小堆的,所以,这里创建的也是小堆。
传入参数
a:数组,n:是数组元素个数
1.为p->a开辟n个空间;
2.利用memcpy函数,把数组a复制到p->a中
3.在使用AdjustUp调整
//建立小堆
void HeapInitArray(HP* p, int* a, int n)
{
//a:数组,n:是数组元素个数
assert(p);
assert(a);
p->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
if (p->a == NULL)
{
perror("malloc fail");
exit(-1);
}
p->size = n;
p->capacity = n;
//把传入数组a复制到p->a中
memcpy(p->a, a, sizeof(HPDataType) * n);
// 向上调整,调整成一个小堆
for (int i = 1; i < n; i++)
{
AdjustUp(p->a, i);
}
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//初始化
void HeapInit(HP* p)
{
assert(p);
p->a = NULL;
p->size = 0;
p->capacity = 0;
}
//销毁
void HeapDestroy(HP* p)
{
assert(p);
free(p->a);
p->a = NULL;
p->size = p->capacity = 0;
}
//插入数据
void HeapPush(HP* p, HPDataType x)
{
//从最后一个位置插入
assert(p);
//扩容
if (p->capacity == p->size)
{
//如果刚开始数组为空,就开辟4个空间。如果不为空,以后每次扩大2倍。
int newcapacity = p->capacity==0 ? 4 : p->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(p->a, sizeof(HPDataType) * p->capacity);
if (tmp == NULL)
{
perror("realloc fial\n");
exit(-1);
}
p->a = tmp;
p->capacity = newcapacity;
}
p->a[p->size] = x;
p->size++;
}
//交换
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向上调整
void AdjustUp(HPDataType* a, int child)
{
//传入数组,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
return;
}
}
//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
//一直交换到数的最后,也就是数组的最后一个位置
while (parent<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
{
return;
}
}
}
//打印二叉树
void HeapPrint(HP* php)
{
assert(php);
for (size_t i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
//建立小堆
void HeapInitArray(HP* p, int* a, int n)
{
//a:数组,n:是数组元素个数
assert(p);
assert(a);
p->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
if (p->a == NULL)
{
perror("malloc fail");
exit(-1);
}
p->size = n;
p->capacity = n;
//把传入数组a复制到p->a中
memcpy(p->a, a, sizeof(HPDataType) * n);
// 向上调整,调整成一个小堆
for (int i = 1; i < n; i++)
{
AdjustUp(p->a, i);
}
}
//删除根
void HeapPop(HP* p)
{
assert(p);
assert(p->size > 0);
Swap(&p->a[0], &p->a[p->size - 1]);
--p->size;
AdjustDown(p->a, p->size, 0);
}
//获取根
HPDataType HeapTop(HP* p)
{
assert(p);
assert(p->size > 0);
return p->a[0];
}
//判空
bool HeapEmpty(HP* p)
{
assert(p);
return p->size == 0;
}