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

堆的应用(建堆、堆排序、TOP-K问题)

目录

1.建堆(以大堆为例)

1.1向上调整法建大堆

1.2向下调整法建大堆 

 1.3建堆方法的时间复杂度

2.堆排序(以升序为例)

3.TOP-K问题

筛选前K大


1.建堆(以大堆为例)

给定一个数组,在此基础上进行建大堆操作

1.1向上调整法建大堆

模拟堆插入数据的操作,从数组的第二个元素开始进行向上调整操作,直到最后一个元素调整结束为止,最终可实现大堆

原理:每一次向上调整(算法正确的前提下)都会将所给数据构成的树实现为大堆

 

void AdjustUp(HpDateType* a, int child)
{//给定数组为空时,没必要进行下面步骤assert(a);int parent = (child - 1) / 2;//parent恒大于等于0while (child > 0){if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}//向上调整法建大堆,模拟插入的过程  O(N*logN)
for (int i = 1; i < n; ++i)
{AdjustUp(a, i);
}

1.2向下调整法建大堆 

思路:从倒数第一个非叶子结点开始进行向下调整操作,直到根节点为止,最终可实现大堆

原理:每一次向下调整(算法正确的前提下)都会将以该节点为根节点的树调整为大堆

 

void AdjustDown(HpDateType* a, int n, int parent)
{int child = parent * 2 + 1;//有左孩子才进行调整while (child < n)//n是节点个数{//左孩子一个逻辑,右孩子一个逻辑,直接假设//child是左右孩子中较大的一个孩子的下标//注意右孩子的有无(防止越界访问与无效数据)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;}}
}//向下调整法建大堆  O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{AdjustDown(a, n, i);
}

 1.3建堆方法的时间复杂度

调整法建堆,时间复杂度是O(N*logN)

调整法建堆,时间复杂度是O(N)

显然, 向调整法建堆 优于 向调整法建堆

记忆方法:向下建堆时,节点数越多的所在层的节点调整次数越少,节点数越少的所在层的节点调整次数越多;向上建堆时,节点数越多的所在层的节点调整次数越多,节点数越少的所在层的节点调整次数越少

可通过计算最坏情况下树高为h的满二叉树的调整次数得出相应时间复杂度的结论(需结合树高与节点数的关系以及错位相减法的知识点)

亦可通过估算最坏情况下底层节点数据的调整次数得出相应时间复杂度的结论,因为底层数据几乎占所有数据的一半

2.堆排序(以升序为例)

排升序,建大堆;排降序,建小堆

思路:先将所给数组进行建堆操作,再将堆顶元素与末尾元素进行交换,然后在除了末尾元素之外的数据基础上进行向下调整操作,使得次大元素位于堆顶,然后再将次大元素与倒数第二个元素进行交换........如此循环,直到堆顶元素与数组第二个元素进行交换后为止,最终可实现升序

void HeapSort(int* a, int n)
{//向下调整法建大堆  O(N)for (int i = (n - 1 - 1) / 2; i >= 0; --i){AdjustDown(a, n, i);}//排升序,建大堆  O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}

堆排序的原理:通过逐步缩小堆的范围来排序数组,当堆中只剩一个元素时(即 end = 0),排序过程已完成

 所以 end > 0 作为循环结束条件

3.TOP-K问题

TOP-K问题:求数据中前K个最大的元素或者最小的元素,一般情况下数据量都比较大

 求前K个最大的数据的思路

(1)构建一个大小为K的小根堆,初始时存入数组前K个元素,并调整为堆结构。此时堆顶是堆内K个元素的最小值,作为全局前K大的“门槛”

(2)遍历剩余数据:若当前元素 大于堆顶,说明它可能属于前K大,于是替换堆顶,并执行向下调整操作;若当前元素 小于等于堆顶,则跳过(因为它不可能属于前K大)。

最终堆中保留的即为全局前K大的元素

原理:

(1)小堆的堆顶始终是当前候选集中的最小值,插入的较大元素会通过堆调整找到正确位置(不一定是叶子节点),而堆顶始终更新为新的最小值,从而保证堆内元素的正确性

 不用大堆是因为如果所有数据中的最大值位于大堆堆顶,那么就无法通过有效的比较逻辑筛选出余下的较大值

 求前K个最小的数据的思路

(1)构建一个大小为K的大根堆,初始时存入数组前K个元素,并调整为堆结构。此时堆顶是堆内K个元素的最大值,作为全局前K小的“门槛”

(2)遍历剩余数据:若当前元素 小于堆顶,说明它可能属于前K小,于是替换堆顶,并执行向下调整操作;若当前元素 大于等于堆顶,则跳过(因为它不可能属于前K小)。

最终堆中保留的即为全局前K小的元素

原理:

(1)大堆的堆顶始终是当前候选集中的最大值,插入的较小元素会通过堆调整找到正确位置(不一定是叶子节点),而堆顶始终更新为新的最大值,从而保证堆内元素的正确性

 

 不用小堆是因为如果所有数据中的最小值位于大堆堆顶,那么就无法通过有效的比较逻辑筛选出余下的较小值

筛选前K大

void PrintTopK(const char* file, int k)
{FILE* fp = fopen(file, "r");if (fp == NULL){perror("fopen fail");return;}//存储前k个int* a = (int*)malloc(sizeof(int) * k);if (a == NULL){perror("malloc fail");return;}for (int i = 0; i < k; ++i){fscanf(fp, "%d", &a[i]);}//建小堆for (int i = (k - 2) / 2; i >= 0; --i){AdjustDown(a, k, i);}//读取后续并比较int val = 0;int ret = fscanf(fp, "%d", &val);while (ret != EOF){if (val > a[0]){Swap(&val, &a[0]);AdjustDown(a, k, 0);}ret = fscanf(fp, "%d", &val);}for (int i = 0; i < k; ++i){printf("%d ", a[i]);}fclose(fp);fp = NULL;free(a);a = NULL;
}

http://www.dtcms.com/a/266943.html

相关文章:

  • 网安系列【3】之深入理解内容安全策略(CSP)
  • 迁移Ubuntu启动文件到另一块硬盘
  • ubuntu 18.04配置镜像源
  • 操作Choose Boot Java Run time for the IDE 导致AS重新安装后依然无法启动(已解决)
  • 考研408《计算机组成原理》复习笔记,第三章(3)——多模块存储器
  • Web前端:全选框的使用
  • Abase和ByteKV存储方案对比
  • 【C#】入门
  • tmux 左下角会话名显示不全的解决方法
  • SpringBoot-规划多模块目录结构
  • 项目介绍:Awesome System Prompts
  • 免费PDF处理软件,支持多种操作
  • 开源项目XYZ.ESB:数据库到数据库(DB->DB)集成
  • 系统架构师
  • Class5多层感知机的从零开始实现
  • Linux awk 命令
  • 浅谈 webshell 构造之如何获取恶意函数
  • chrome插件合集
  • 4 位量化 + FP8 混合精度:ERNIE-4.5-0.3B-Paddle本地部署,重新定义端侧推理效率
  • 【LUT技术专题】CLUT代码讲解
  • 写一个Ununtu C++ 程序,调用ffmpeg API, 来判断一个数字电影的视频文件mxf 是不是Jpeg2000?
  • MSPM0G3507学习笔记(一) 重置版:适配逐飞库的ti板环境配置
  • 服装零售企业跨区域运营难题破解方案
  • 深度学习笔记29-RNN实现阿尔茨海默病诊断(Pytorch)
  • 25年Java后端社招技术场景题!
  • MyDockFinder 绿色便携版 | 一键仿Mac桌面,非常简单
  • 应用分发平台的重要性:构建、扩展和管理您的移动应用
  • VR 火化设备仿真系统具备哪些优势?​
  • MySQL 八股文【持续更新ing】
  • 机器学习路径规划中的 net 和 netlist 分别是什么?