---------------说起堆,很难不去联想到二叉树。
目录
那如何建大堆呢?或者如果 数组序列不符合 向下调整法的前提,又该怎样做?
④小插曲~ 为什么建堆的时间复杂度为O(n)? 那么堆排序的时间复杂度是多少呢?
(1)堆的概念以及结构
①为什么未出现堆这个概念?
②堆 有什么性质?
1.堆中某个节点的数值,总是大于或小于 ”左右子树”。
2.堆总是一棵完全二叉树。
根据性质1,也就可以把堆分为大堆、小堆>_
这是一组很平常的数组. 下图所示:
把它们按照二叉树的形式分离:
可以看见,此时每个节点的值,并不满足性质1,所以此时并不构成堆。
进行这样调整后,每个节点的值,都小于左右子树的节点。
此结构为小堆.......
我们把这个方法叫做——"向下调整法”。(之后会代码实现)
但,这个方法有个前提,必须是满足左、右子树都是堆的情况。
(2)堆的实现
①向下调整法 那些事儿
要理解、搞懂堆排序,你不得不弄懂“向下调整法”的思想;以及代码的实现。
向下调整法:
顾名思义,从“堆顶”开始,向下调整。和两个下节点比较。建小堆就把大的数值往下调或建大堆就把小的数值往下调。
根据二叉树的性质,我们可以借助数组的下标,找到每个节点对应的位置:
我们暂时称根为父亲节点(parent),子树为孩子(child)
而,向下调整法,从上往下,是用父亲节点找孩子节点,所以设定函数参数parent好辨识。
我们很难知道到底,左右孩子谁的值小,因此就先假定左孩子小,如果不是就++child。
一旦父节点大于孩子节点,因为建小堆,大的数要往后放,所以父亲节点和孩子节点交换
并更新parent与child节点。
否则,也就是父节点的值,小于孩子节点,也就不用在调整了。因为前提是左右子树都是小堆。
注:细心的朋友或许就发现了上述代码有一处不妥。
如果,当调整一层的节点只有左孩子而没得右孩子呢?
所以我们要加上这一句,防止数组越界~
图示的关系,让我们理解透 向下调整的终止条件。
结果:小堆
那如何建大堆呢?或者如果 数组序列不符合 向下调整法的前提,又该怎样做?
②建堆:
从倒数第一个非叶子节点开始调整,一直调整到根节点的数。
小堆:
大堆在建堆的基础上,也就不用再去考虑左右子树是不是大堆----
改改 调整法中的大小于即可:
③如何用堆实现排序?
说起用堆排序,不同的排序方式需要不同的堆。
比如,如果要排列升序,那么就需要构建大堆!而非小堆。
④小插曲~ 为什么建堆的时间复杂度为O(n)? 那么堆排序的时间复杂度是多少呢?
完全二叉树,可以在一定程度上看成满二叉树:
堆的排序和算法到这里也就告一段落啦~
(3)堆的功能
最后呢,就来写写堆的一些类似 栈、队列的功能。
堆的初始化、堆顶以及堆的有效数个数都很简单,也就不多分析。
堆的增和删:
增加数据,虽然和其他线性表大同小异,但堆毕竟是堆,不能让插入的数据,打乱堆的结构。
因此如何才能让,插入的数,到达他应该去的位置?
删除数据: 堆删除元素,我们采取去堆顶
总结:
①堆特别重要的就是 向下调整法,但他的前提是,根节点的左右子树必须是堆
②如果不是堆,那就从最后一个非叶子节点,进行调整。
③堆增加数据,需要向上调整。
④建堆的时间复杂度为O(n) 向下调整的时间复杂度为O(logN) 堆排序的时间复杂度为O(N*logN).
(4)TopK问题
有没有想过一个情景,给你一个N个数,要你用堆的思想,去查着N个数的钱K个大的(小的)?
面对这样的情景,我们该怎么做?
void AdjustDown(HeapDataType* a, int n, int parent)
{
int child = parent * 2 + 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 = parent * 2 + 1;
}
else
{
break;
}
}
}
void CtreateData(const char* filename, int n)
{
FILE* fout=fopen(filename, "w");
//把数据 放入磁盘文件
srand((unsigned long)time(NULL));
while (n >= 0)
{
fprintf(fout, "%d\n", rand()%10000);
--n;
}
}
void TestTopK()
{
//找N个数中 前K大的数
//建议当 N 无限大 但 k 足够小时
//建小堆 通过小堆遍历N 进行选择 替换堆顶数据 再进行向下调整
//引入读文件
const char* filename = "Data.txt";
int N = 10000;
//CtreateData(filename,N);
FILE* fin=fopen(filename, "r");
//读文件
//读k个数 并建堆
int k = 5;
int* a =(int*)malloc(sizeof(int) * k);
if (a == NULL)
{
perror("malloc");
return;
}
//取数据
for (int i = 0;i < k;++i)
{
fscanf(fin, "%d", &a[i]);
}
//建堆
for (int j =(k-2)/2;j >=0 ;--j)
{
AdjustDown(a, k,j);
}
int val;
while (fscanf(fin, "%d",&val) != EOF) //读文件数据
{
if (val > a[0])
{
a[0] = val;
AdjustDown(a, k, 0);
}
}
for (int i = 0; i < k; ++i)
{
printf("%d ", a[i]);
}
fclose(fin);
}
谢谢你的阅读,祝你好运!~