文章目录
堆
1. 堆的概念及结构
如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维**数组**)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
总结:
大堆: 树中所有父亲都大于等于孩子
小堆: 树中所有父亲都小于等于孩子
孩子和父亲下标的关系: leftchild =parent * 2 + 1(奇数) ; rightchild =parent * 2 + 2(偶数) 。
父亲和孩子下标的关系: parent = (child - 1) / 2
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
2. 堆的实现
文件名 | 功能 |
---|---|
heap.h | 创建堆,完成函数名的声明 |
heap.c | 实现堆的各个功能函数 |
test.c | 测试堆函数的正确性 |
2.1 堆的定义
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size; //堆的元素个数
int capacity; //堆的容量
}HP;
2.2 初始化堆
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->capacity = php->size = 0;
}
2.3 销毁堆
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
2.4 打印堆中的元素
直接for循环打印堆(数组)中的元素
void HeapPrint(HP* php)
{
assert(php);
for (int i = 0; i < php->size; i++)
{
printf("%d ", php->a[i]);
}
printf("\n");
}
2.5 获取堆中的数据个数
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
2.6 获取堆顶元素
直接返回堆顶元素即数组首元素
注意: 堆为空不能获取堆顶元素
HPDataType HeapTop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
2.7 判断堆是否为空
直接返回php -> size是否等于0
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
2.8 堆的插入
类似于顺序表的尾插操作, 先判断堆容量是否需要扩容, 再将元素插入, 最后对堆中元素进行**向上调整**,使它变成堆
void HeapPush(HP* php, HPDataType x)
{
assert(php);
//扩容
if (php->capacity == php->capacity)
{
int newCapacity = (php->capacity == 0) ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, newCapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
php->a = tmp;
php->capacity = newCapacity;
}
php->a[php->size] = x;
php->size++;
//向上调整
AdjustUP(php->a, php->size-1);
}
2.9 堆的向上调整算法
思路: 找到要插入元素的的父结点与之对比交换大小
先通过父结点和子结点关系算出父结点元素下标然后循环比较二者的大小, 大小关系不满足堆则交换元素继续比对, 否则直接跳出循环
注意: 下面是调整成大堆的代码
void Swap(HPDataType* a, HPDataType* b)
{
HPDataType tmp = *a;
*a = *b;
*b = tmp;
}
void AdjustUP(HPDataType* a, int child)
{
int 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;
}
}
}
2.10 堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据交换,然后删除数组最后一个数据,再进行向下调整算法。
注意: 堆为空不能删除元素
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
//交换并删除
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
//向下调整
AdjustDown(php->a, php->size,0);
}
2.11 堆的向下调整算法
思路: 找出孩子结点中最大的那个, 与之对比交换大小
先通过父结点和子结点关系算出子结点元素下标,然后循环找出左右孩子中最大的那个且保证左右孩子都在堆的范围内;当孩子大于父亲,二者交换继续向下调整, 否则调整结束跳出循环
注意: 下面是调整成大堆的代码
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;
while (child < n)
{
//1. 找出孩子结点中最大的那个并保证child的范围
if ( child + 1 < n && a[child+1]>a[child])
{
++child;
}
//2. 孩子大于父亲, 交换, 继续向下调整 ; 孩子小于父亲则调整结束
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent=child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
2.12 堆的构建
法1
思路: 直接复用HeapPush函数以向堆中插入数据的方式向上调整来建堆
for (int i = 0; i < n; i++)
{
HeapPush(&hp, arr[i]);
}
时间复杂度
O(nlogn)
法2
从最后一个元素父结点开始建堆(因为最后单独一行是可以当成树的, 所以可以从最后一个元素的父结点考虑向下开始调整成树)
void HeapCreate(HP*php, HPDataType*a, int n)
{
assert(php);
php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
if (php->a == NULL)
{
perror("malloc fail");
exit(-1);
}
memcpy(php->a, a,sizeof(HPDataType)*n);
php->size = php->capacity = n;
for (int i = (n - 1 - 1) / 2; i >= 0; --i) //i是最后一个结点的父结点
{
AdjustDown(php->a, n, i);
}
}
时间复杂度
O(n)
对比
对比两种堆的构建方式, 向上调整算法建堆时间复杂度: O(nlogn) ; 向下调整算法建堆时间复杂度: O(n), 所以推荐法2的向下调整算法来建堆