前置理解 帮助学习 学习目的
堆 是一种有 父子间大小 关系要求 的 完全二叉树
所以分大堆 和 小堆
建堆 就是 将一棵完全二叉树 通过调整算法 实现 刚才的关系就好
因此 堆有这样的 特点 堆 顶 必然是 这个数组里的最值
通过 依次拿取堆顶数据 后继续调整 建堆 再 拿去
首先拿到的是 最大值 其次是第二大的 再其次是第三大的
就这样可以实现对该树 (数组)中的数据 进行 有序读取
间接实现了 排序
push 的目的是为了 保持堆的性质
pop 是为了取出堆顶的数据 能得到出最值
或者说 有了 排序的特点 就是 堆排序 算法的前世 前传
1 区分数据结构中的堆栈 和 操作系统里的 堆 栈帧 区分
💡 关键误区**:名字相同,概念完全 无关**
一、左侧:数据结构层面(逻辑结构,和内存无关)
二、右侧:操作系统内存分区(物理内存,程序运行时的存储空间)

2 简单分类 大根堆 和 小根堆
1 都是基于 完全二叉树
2 只要求父子之间的大小关系 对于兄弟节点之间并没有 要求
因此 不会得出 大根堆 一定的降序 小根堆一定是 升序的结论 纯属偶然
堆是完全二叉树,通常用数组顺序存储,分两类:大根堆、小根堆。
1. 大根堆(大顶堆)
父节点 ≥ 左右两个子节点的值 ,堆顶(树根)是整棵堆里最大值。
公式:arr[父] ≥ arr[左孩子] && arr[父] ≥ arr[右孩子]
2. 小根堆(小顶堆)
父节点 ≤ 左右两个子节点的值 ,堆顶(树根)是整棵堆里最小值。
公式:arr[父] ≤ arr[左孩子] && arr[父] ≤ arr[右孩子]
简单记忆口诀: x堆 父亲节点是该子树里 的 相对x 值
大堆 该子树里 较大值 小堆 该子树里 较小值
3 堆 的 用途
1. TOP-K 问题(最常用)
① 求前 K 大 → 小根堆(容量 = K)
先把前 K 个数入堆,构建小根堆(堆顶是当前 K 个里最小值)
剩下每个数:比堆顶大就替换堆顶、堆化;否则跳过
最终堆内就是 最大的 K 个数字 比方一亿个数中最小的十个数
2. 堆排序
根堆实现升序排序:
### 无序数组建成大根堆(堆顶是最大值)
### 堆顶和末尾元素交换,末尾固定为有序值,剩余数组重新堆化
### 循环往复,数组从后往前生成有序序列
4 堆的实现
one

two
three

5 **数组(顺序表)**插入数据后 利用向上调整算法
大顶堆上浮(向上调整 ) 完整逻辑梳理
起点下标 = 新插入元素的数组末尾下标(尾插位置)
循环拿当前节点 和父节点 对比: 大顶堆规则:父 ≥ 左右孩子,任意子树根是子树最大值
### 当前节点>父节点:违背大顶堆规则 → **父子交换**,更新当前下标为父下标,继续向上找 新父节点 比对
### 当前节点 ≤ 父节点:满足堆性质 → 上浮终止
边界:当前下标走到0(根节点),没有父节点,直接结束。
所以最少就是一次都不要调整 插入就满足堆的规则 
最坏是 层数-1 ==边数 就是该叶节点的值 比它到根路径上所有的祖先节点的值都小 就是需要交换 边数次 就是 有几个祖先 就是交换几次
中间 的都有可能 
需要利用之前的 下标寻找父亲的方法
变相说明 堆 是一棵完全二叉树 但是 有限制 有父子关系规则

实际存储结构 物理结构就是数组 插入就是尾插 + 向上调整 (上浮)
- 物理结构:数组(连续内存),逻辑结构:完全二叉树;
- 新增元素第一步永远数组尾部插入,堆的特性靠上浮维护;
- 完全二叉树特性决定堆能用数组无空隙存储,没有空洞浪费空间
如果是大堆的话 就是 根节点最大
-- 拓宽就是 每一棵子树的根节点 是最大的
总结 不就是 任意二叉堆 的堆顶 是 最值
利用最开始插入位置的下标 作为起点下标
1 用 该节点的值 与它的父亲节点比较
如果不满足 父子关系 就可以利用向上调整算法 来实现 节点交换
结束的条件是 任意一棵子树 总满足 父亲节点的值 >= 子节点
但是兄弟之间并未要求
最后 尾插定点位,从该下标往上,大顶堆孩子比爹大就互换,直到爹≥孩子或到树根。
6 插入数据到 堆里
前置理解
实际上的 堆 就是 完全 二叉树
我们用 连续存储 (数组)的方式(注 :也可以用链表 ) 来实现 堆
尽管我们控制的是 下标和 数组 我们要想象 控制的是二叉树
但我们这里先用 数组 (顺序表)来实现
第一步 传入孩子的下标
第二步 计算最近的父亲下标
第三步 比较 父亲和孩子节点 满足堆的要求 就break 退出循环
不满足 就去交换父子 关系 倒反天罡一次 {
里面有 1 交换父子值
2 更新下标 原来的父亲下标 更新为新的儿子 下标
向上找新的父亲 继续循环比较
3 利用新的儿子下标 锁定新的 父亲的下标
4 只要 child 的下标 = 0 || break 时 结束循环
}

如 上图中 最开始是判断 parent>0实际上是巧合 刚好 歪打正着
就最坏情况 当最后一次父子交换时 parent = 0 child = 1
此时 parent = =child = =0; 依旧会进入循环的 parent>=0 就进的
继续画图了解 
这样结束了 纯属狗运 就是 石山代码 哈哈哈 
和上面这个网图 一样 尽管都可以实现 但是更推荐 使用child >0 的
最坏情况下会少进入一次循环 的 下面这次就不会进去
7 建 堆 过 程 将下面的数 建堆 就可以看作 将下列数据依次 Push 插入之后
利用向上调整算法 就自然 形成 一个堆了 微分思想
拆解问题 将大的问题拆解为可执行的小步骤 循环执行 

最后就是一个小堆 上图就是这样子 的
8 删除 pop 堆顶的数据 就是 删除 根节点
主要是覆盖的方法 就会使得 关系乱了 这是表象
不能使用挪动覆盖 删除
本质是 兄弟之间的 大小 是没有限制和要求的
使得 新形成的父子之间 可能是原来的兄弟关系 也是没有大小关系的 就不是堆

解决之道 在于 狸猫换太子 假戏真做 在替身 消失
1 就是将根节点 和 最下层最 右边的节点 交换位置
2 再将最后那个在数组里尾删 就好了 size--
3 利用 向 下调整算法 进行调整 前提是根出发的左右子树都是 成堆的 都是堆
如果是 小堆 向下调整时 大的 向下 这是相对的 小的向上
区别 在于 出发点 向下调整就是 从根出发的
而 向上调整 是从叶节点开始的 

间接实现 (伪)堆排序打印


9 无论是向下调整建堆 和 向上调整建堆 单次调整 时间复杂度 都是 log N 的
头文件
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
void HPInit(HP* php);
void HPDestroy(HP* php);
void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);
HPDataType HPTop(HP* php);
bool HPEmpty(HP* php);
源码 源文件
#include"Heap.h"
void HPInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
void HPDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{
// 初始条件
// 中间过程
// 结束条件
int parent = (child - 1) / 2;
//while (parent >= 0)
while (child > 0)
{
if (achild < aparent)
{
Swap(&achild, &aparent);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HPPush(HP* 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->aphp-\>size = x;
php->size++;
AdjustUp(php->a, php->size - 1);
}
void AdjustDown(HPDataType* a, int n, int parent)
{
// 先假设左孩子小
int child = parent * 2 + 1;
while (child < n) // child >= n说明孩子不存在,调整到叶子了
{
// 找出小的那个孩子
if (child + 1 < n && achild + 1 < achild)
{
++child;
}
if (achild < aparent)
{
Swap(&achild, &aparent);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// logN
void HPPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a0, &php->aphp-\>size-1);
php->size--;
AdjustDown(php->a, php->size, 0);
}
HPDataType HPTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a0;
}
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}





