目录
1.堆的性质
1.堆总是一颗完全二叉树
2.堆顶父亲节点总是大于(大堆)或者小于(小堆)孩子节点
2.数组构建堆的方法
2.1 方法一:堆向下调整算法
接下来易构建小堆为例
根据学过的知识可以知道;最后一个非叶子节点=(最后的叶子节点-1)/2;
调整完最后一个非叶子节点后就可以往前调整,知道调整到根节点
具体的调整方法如下图
具体代码的实现
//一共有n个节点,数组为arr
//先找到最后一个非叶子节点
int parent=(n-1-1)/2;
//从最后一个非叶子节点一直开始调整到根节点
for(int i=parent;i>=0;i--)
{
AdjustDown(arr,n,i);
}
AdjustDown的代码如下
void AdjustDown(int* arr, int n, int parent)
{
int child = parent * 2 + 1;
while (child<n)
{
if (child + 1 < n)
{
if (arr[child] >arr[child + 1])
child++;
}
if (arr[child] <arr[parent])
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
最后的结果明显是符合小堆的;
2.2 方法二:堆的插入
还是一构建小堆为列
具体代码的实现
void AdjustUp(int* arr, int n, int child)
{
int parent = (child - 1) / 2;//
while (child > 0)//为什么不能用parent
{
if (arr[child] > arr[parent])
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
注意:上面代码while循环的结束条件不能为parent>=0;如果插入的数据足够小,需要一直向上调整到根节点,这个时候parent==0;当执行玩child=parent时,chiid==0,此时在执行parent=(child-1)/2时,就会得到parent==0;此时该程序就不会跳出循环。最终出现bug;
3.构建堆的时间复杂度的问题
3.1方法一:向下调整的时间复杂度:
3.2 方法二:堆插入向上调整的时间复杂度
4.堆排序
4.1用堆实现升序
思考:实现升序是构建小堆还是大堆?
如果构建的是小堆,第一次可以得到数组中的最小值,但是如何得到次小值呢? 继续构建一个堆的话会的到次小值,此时拍完之后,一共构建了n次堆如果用方法一构建堆,那么时间复杂度为O(n)为n^2,这时候还不如直接进行冒泡排序更贱直接。所以,在排升序的时候要建大堆,因为选出最大值后,将最大值后最后一个节点的值进行交换,再把堆的大小减减,这样在这个堆的范围内最大的值就会被排到末尾,循环往复就会得到一个降序的数组。
具体代码实现如下
//建堆过程
int parent = (n - 1 - 1) / 2;
for (int i = (n - 2) / 2; i >= 0; i--)
{
AdjustDown(arr, n, i);
}
int end = n - 1;
for (end = n - 1; end > 0; end--)
{ //交换根节点和最后一个叶子节点
Swap(&arr[end], &arr[0]);
AdjustDown(arr,end, 0);
}
得到的结果
4.2 TopK的问题
TopK的问题就是堆排序的应用。
案例:如果要从10亿个数据中找到5个最大值。
这种情况下,对数据进行排序是非常不合理的,因为会占用极大的内存,这个时候就体现了堆排序的优点,先建立5个数据的小堆,在对数据进行比较,如果某一个数据大于小堆里面的最大值,就将小堆的根节点和该数据进行交换,在对新的堆进行向下调整,如此往复,就能得到最大的5个值。