个人主页: 深情秋刀鱼@-CSDN博客
数据结构专栏:数据结构与算法
源码获取:数据结构: 上传我写的关于数据结构的代码 (gitee.com)
目录
一、堆
1.堆的概念
堆是一棵完全二叉树,且其中的节点总是不大于(或不小于某个值)。如果堆中的节点总是不大于某个值(根节点最大),称为大根堆;如果堆中的节点总是不小于某个值(根节点最小)将根节点最小的堆称为小根堆。
2.堆的定义
二、堆的实现
1.初始化和销毁
2.插入
堆在内存中是以数组的形式存储的,在逻辑上需要将数组看成一棵完全二叉树。向堆中插入数据时要保证堆的结构不被破坏,并将其调整为小根堆或大根堆时需要用到向上调整算法。
向上调整算法
- 图解(小根堆):
- 代码实现:
//插入
void HPPush(Heap* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("realloc fail!");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size++] = x;
AdjustUP(php->a, php->size - 1);
}
3.删除
删除规定只删除堆顶元素(删除堆尾元素size--即可),删除堆顶元素的同时需要保持结构不变,需要用到向下调整算法。
向下调整算法
- 图解(小根堆):
- 代码实现:
//删除(删除堆顶的数据)
void HPPop(Heap* php)
{
assert(php && php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
4.取堆顶元素
5.判空
三、Top_k问题
1.问题描述
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
简单来说:以求取数组中前六个最大的元素为例,将一个元素个数为n的数组调整为大堆后,堆顶的元素就是数组中n个元素的最大值,获取堆顶元素后将堆顶元素删除(删除的步骤是堆顶元素先与堆尾元素交换,在堆尾删除堆顶元素),通过向下调整算法调整堆的结构使其仍然呈大堆排列,排列之后新的堆顶元素就是数组中n-1个元素中的最大值,依此类推。
- 代码实现:
- 运行结果
2.面试中的Top_k问题
- 给出N个整数,存储在磁盘文件中,要求取出最大的前k个元素。
- 给出N个整数,存储在磁盘文件中,要求取出最大的前k个元素且占用的内存空间不允许超过1KB。
四、堆排序
1.建堆
给定一个数组,要求将其调整为大堆或小堆。我们可以将原数组直接看成一棵完全二叉树,然后利用向上或向下调整算法将其调整为大堆或小堆,大堆和小堆是可以自由切换的,只需要更改向下和向上调整算法中的比较逻辑即可。
2.堆排序
堆排序即利用堆的思想来进行排序,总共分为两个步骤:
算法逻辑:以降序建小堆为例,一个元素个数为n的数组调整为小堆后,堆顶元素是数组中的最小元素,将堆顶元素与堆尾交换,然后对新的堆顶元素向下调整,一直调整到合适的位置再形成堆,此时堆顶元素应为数组中次小的元素,将次小的元素与第n-1个元素(倒数第二个)交换,再利用向下调整算法调整结构,依此类推。
代码实现:
//向下调整算法
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = 2 * parent + 1;
while (child < n)
{
if (child + 1 < n && a[child] > a[child + 1])
child++;
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
//交换
void Swap(HPDataType* a, HPDataType* b)
{
HPDataType tmp = *b;
*b = *a;
*a = tmp;
}
//堆排序(O(N*logN))
void HPSort(HPDataType* a, int n)
{
//降序:建小堆
//升序:建大堆
//for (int i = 1; i < n; i++)
// AdjustUP(a, i);//向上调整建堆
for (int i = (n - 1 - 1) / 2; i < n; i++)
AdjustDown(a, n, i);
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
图解(升序大根堆):
五、堆的时间复杂度
1.建堆
a.树中高度与节点的关系
设有一棵高度为h的满二叉树,如下图:
根据递推公式我们可以得到节点N与高度h的关系:F(h)=2^0+2^1+2^2+.....+2^(h-1)。根据等比数列求和公式,F(h)=2^h-1。
一棵完全二叉树节点最多的情况是一棵满二叉树(最后一层全满),节点最少的情况是最后一层有且仅有一个节点的情况。