当前位置: 首页 > news >正文

【数据结构 · 初阶】- 堆的实现

目录

一.初始化

二.插入

 三.删除(堆顶、根)

四.整体代码

Heap.h

Test.c

Heap.c


我们使用顺序结构实现完全二叉树,也就是堆的实现

以前学的数据结构只是单纯的存储数据。堆除了存储数据,还有其他的价值——排序。是一个功能性的数据结构

小根堆堆顶的数据一定是最小的,大根堆堆顶的数据一定是最大的
选出最大/最小,再选次大/次小……不断选最后就帮助排序。还可以解决取前几,后几的TOP-K 问题 

我们以建大堆为例。建小堆只需改变 爸 < 娃 即可


一.初始化

下面多次用到交换,将交换分装成函数

Heap.h

typedef int HPDataTypt;typedef struct Heap
{HPDataTypt* a; // 数组指针,指向要开辟的存储数据的数组int size; // 当前已存储的有效数据个数int capacity; // 最大容量
}HP;void HeapInit(HP* php); // 初始化
void HeapDestroy(HP* php); // 销毁

Heap.c 

void HeapInit(HP* php)
{assert(php);php->a = (HPDataTypt*)malloc(sizeof(HPDataTypt) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}void Swap(HPDataTypt* p1, HPDataTypt* p2)
{HPDataTypt tmp = *p1;*p1 = *p2;*p2 = tmp;
}

php 是指向主函数中,HP(结构体类型)的变量 hp 地址的指针。php 中存放的是 hp 的地址。若为空就说明结构体没有开好,所以一定不能为空,断言。

二.插入

堆的底层就是数组,可以插入数据。要把控制数组想象成控制树。原来是大根堆,插入后,要求还得是堆。
插入前是堆,插入后会影响部分祖先(跟祖先调整)

以大根堆为例,看最简单的情况:插入20,插入后不影响堆的性质。

    

再插入60,插入后要调整。 

为保证父亲 > 娃,要交换   

娃 还> 父亲,继续交换 父亲 > 娃,结束

上面的过程叫 向上调整 ,最多调整高度次,时间复杂度:O( log N )。插入一个数据,想让他再调整成堆只要 log N 次


堆的插入不像链表、顺序表,不能想往哪插就往哪插,要保持性质。

上面的尾插,如果堆原来是这样,就不能尾插20 

所以插入单纯的叫 Push 就好,因为不是由接口指定在哪个位置插入。


void AdjustUp(HPDataTypt* 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; // 娃 <= 爸,不往上走}}
}void HeapPush(HP* php, HPDataTypt x)
{assert(php);if (php->size == php->capacity){HPDataTypt* tmp = (HPDataTypt*)realloc(php->a, sizeof(HPDataTypt) * php->capacity * 2);if (tmp == NULL){perror("malloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1); // 从孩子(插入数据)位置向上调整
}

 三.删除(堆顶、根)

堆删除,删尾轻松,但无意义。

为什么删堆顶、根才有意义?老大被干掉了,老二才能冒头。

堆实现的意义,无论是排序还是 top-k ,本质是在帮我选数,选出最大/最小数

删除后,也要保证是堆。要把最大的删掉,怎么搞?


不能挪动删除(直接删)!原因:1.效率低下   2.父子兄弟关系全乱了


正确方法:(间接删) 堆顶和最后的元素换一下;--size,使换下去的最后一个(原堆顶)元素失效
1.效率高   2.最大程度的保持了父子关系

单看左右子树依旧是大堆,换上去的原最后元素大概率是比较小的,就要向下调整


看下面的新场景:为保证换了之后父亲 > 娃,现在的堆顶(原最后一个元素)要跟大的娃换。

娃中大的 > 爸,把爸换下去 继续换     

最坏情况调到叶子结束。物理上是数组,怎么判断到叶子——没有娃,怎么判断没有娃呢?
把它当做爸,算左娃的下标,如果超出数组范围就没娃,所以参数要多给个数组的大小 size,用来判断 child 是否越界

最坏走高度 log N 次

void AdjustDown(HPDataTypt* a, int n, int parent)
{int child = parent * 2 + 1; // 默认左孩子大,将左孩子定为 childwhile (child < n){// 选出左右孩子中大的那一个if (child + 1 < n && a[child] < a[child + 1]) // 防止无右娃的越界风险{child++; // 如果右孩子大,++后,child 就是右孩子}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}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); // 向下调整,0是开始调整位置的下标// n 是有效数据个数,作为下标,用来判断 child 是否越界
}

向上调整的前提:除了 child 这个位置,前面的数据构成堆
向下调整的前提:保证左右子树都是堆

四.整体代码

Heap.h

typedef int HPDataTypt;typedef struct Heap
{HPDataTypt* a; // 数组指针,指向要开辟的存储数据的数组int size; // 当前已存储的有效数据个数int capacity; // 最大容量
}HP;void HeapInit(HP* php); // 初始化
void HeapDestroy(HP* php); // 销毁void HeapPush(HP* php, HPDataTypt x); // 插入
void HeapPop(HP* php);// 删除堆顶HPDataTypt HeapTop(HP* php); // 堆顶的数据
bool HeapEmpty(HP* php); // 探空
int HeapSize(HP* php);void AdjustUp(HPDataTypt* a, int child); // 向上调整
void AdjustDown(HPDataTypt* a, int n, int parent); // 向下调整

Test.c

void test1() // 排序
{HP hp;HeapInit(&hp);HeapPush(&hp, 2);HeapPush(&hp, 45);HeapPush(&hp, 76);HeapPush(&hp, 23);HeapPush(&hp, 5654);HeapPush(&hp, 24);HeapPush(&hp, 5);HeapPush(&hp, 242);HeapPush(&hp, 25);while (!HeapEmpty(&hp)){printf("%d ", HeapTop(&hp));HeapPop(&hp);// 选老二,必须干掉老大}HeapDestroy(&hp);
}void test2() // top-k
{HP hp;HeapInit(&hp);HeapPush(&hp, 2);HeapPush(&hp, 45);HeapPush(&hp, 76);HeapPush(&hp, 23);HeapPush(&hp, 5654);HeapPush(&hp, 24);HeapPush(&hp, 5);HeapPush(&hp, 242);HeapPush(&hp, 25);HeapPush(&hp, 5);HeapPush(&hp, 5);int k = 0;scanf("%d", &k);while (!HeapEmpty(&hp) && k--){printf("%d ", HeapTop(&hp));HeapPop(&hp);// 选老二,必须干掉老大}HeapDestroy(&hp);
}

Heap.c

void HeapInit(HP* php)
{assert(php);php->a = (HPDataTypt*)malloc(sizeof(HPDataTypt) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}void Swap(HPDataTypt* p1, HPDataTypt* p2)
{HPDataTypt tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustUp(HPDataTypt* 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; // 娃 <= 爸,不往上走}}
}void HeapPush(HP* php, HPDataTypt x)
{assert(php);if (php->size == php->capacity){HPDataTypt* tmp = (HPDataTypt*)realloc(php->a, sizeof(HPDataTypt) * php->capacity * 2);if (tmp == NULL){perror("malloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1); // 从孩子(插入数据)位置向上调整
}void AdjustDown(HPDataTypt* a, int n, int parent)
{int child = parent * 2 + 1; // 默认左孩子大,将左孩子定为 childwhile (child < n){// 选出左右孩子中大的那一个if (child + 1 < n && a[child] < a[child + 1]) // 防止无右娃的越界风险{child++; // 如果右孩子大,++后,child 就是右孩子}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}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); // 向下调整,0是开始调整位置的下标// n 是有效数据个数,作为下标,用来判断 child 是否越界
}HPDataTypt HeapTop(HP* php)
{assert(php);return php->a[0];
}bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}int HeapSize(HP* php)
{assert(php);return php->size;
}void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = php->capacity = 0;
}

相关文章:

  • 乐迪电玩发卡查分与控制面板模块逻辑解析
  • 中电金信联合阿里云推出智能陪练Agent
  • 华为S系列交换机CPU占用率高问题排查与解决方案
  • 3、有Bluetooth,LCD,USB,SD卡,PSRAM,FLASH、TP等软硬件驱动开发经验优先考虑
  • PyTorch 分布式 DistributedDataParallel (DDP)
  • Langgraph实战-Agent-ReAct(Reason+Act)概述
  • 扩散模型(Diffusion Models)
  • 客户对质量不满意,如何快速响应?
  • 基于Transformer与随机森林的多变量时间序列预测
  • 商会携手会员单位博阳机械举办DeepSeek大模型技术及应用分享会
  • 02-keil5的配置和使用
  • xxl-job 入门
  • 2176. 统计数组中相等且可以被整除的数对
  • ECharts散点图-散点图15,附视频讲解与代码下载
  • 智能体MCP 实现数据可视化分析
  • 第44讲:玩转土壤数据!用机器学习挖掘地球皮肤的秘密 [特殊字符][特殊字符]
  • 王智:从“秋雅”到“高淑华”,影视双栖的破局者如何赢得口碑长虹?
  • Python常用的第三方模块之数据分析【pdfplumber库、Numpy库、Pandas库、Matplotlib库】
  • 什么是空值合并运算符[Nullish coalescing operator](??)?
  • Spark与Hadoop之间的联系和对比
  • IPO周报|本周暂无新股申购,上周上市新股中签浮盈均超1.6万
  • 人大书报资料中心与中科院文献中心共筑学科融合创新平台
  • 特写|为何这么多人喜欢上海半马,答案藏在他们的笑容里
  • 申花迎来中超三连胜,这一次终于零封对手了
  • 上海崇明“人鸟争食”何解?检察机关推动各方寻找最优解
  • 从黄仁勋到美国消费者,都在“突围”