数据结构--堆
一. 堆
1. 堆的概念
堆(heap):一种有特殊用途的数据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中查找最值。
堆在物理层面上,表现为一组连续的数组区间:long[] array ;将整个数组看作是堆。
堆在逻辑结构上,一般被视为是一颗完全二叉树。
满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆;反之,则是小堆,或者小根堆,或者最小堆。当一个堆为大堆时,它的每一棵子树都是大堆。

2. 堆的存储方式
从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储;
假设 i 为结点在数组中的下标,则有:

如果 i 为 0,则 i 表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2;
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子;
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子。
二. 堆的基本操作
1. 创建堆,向下调整与向上调整
创建堆只有两种堆可以创建,要不就是大根堆,要不就是小根堆。而要满足大根堆还是小根堆的逻辑,就要向下调整的操作才能实现。要想自己实现堆,堆本身就是一个数组,因此创建一个数组来创建堆。
对于集合 { 27,15,19,18,28,34,65,49,25,37 } 中的数据,如果将其创建成堆呢?

仔细观察上图后发现:根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可。 向下过程(以小堆为例):
- 让 parent 标记需要调整的节点,child 标记 parent 的左孩子(注意:parent 如果有孩子一定先是有左 孩子)
- 如果 parent 的左孩子存在,即: child < size, 进行以下操作,直到 parent 的左孩子不存在:
- 看 parent 右孩子是否存在,存在找到左右孩子中最小的孩子,让 child 进行标
- 将 parent 与较小的孩子 child 比较,如果:
- parent 小于较小的孩子 child,调整结束;
- 否则:交换 parent 与较小的孩子 child,交换完成之后,parent 中大的元素向下移动,可能导致子树不满足对的性质,因此需要 继续向下调整,即 parent = child;child = parent*2+1;然后继续 2

def sift(li, low, high):"""建立大根堆:param li: 列表:param low: 堆的根节点位置:param high: 堆的最后一个元素的位置:return:"""i = low # 最开始指向根节点j = 2 * i + 1 # 开始是左孩子tmp = li[low] # 把堆顶存起来# 只要j位置有数while j <= high:# 左孩子和右孩子比较大小 右孩子有没有越界 且 右孩子比左孩子大if j + 1 <= high and li[j + 1] > li[j]:j = j + 1 # 把j指向右孩子# 比较堆顶的tmp和j左右孩子大小比较if li[j] > tmp: # 如果孩子比堆顶大li[i] = li[j] # 把孩子大的换到上面父节点# 往下看一层,将i移动到孩子位置,将j继续向下移动到新i的孩子的位置i = jj = 2 * i + 1else: # tmp更大,把tmp放到i的位置上 结束循环li[i] = tmp # 把tmp放到某一级领导位置上break# 越界了else:li[i] = tmp # 说明i走到最下面一层了,j到还要下一层没有叶子节点位置,是空的,就把tmp放到叶子节点上
def sift(li, low, high):"""建立小根堆:param li: 列表:param low: 堆的根节点位置:param high: 堆的最后一个元素的位置:return:"""i = low # 最开始指向根节点j = 2 * i + 1 # 开始是左孩子tmp = li[low] # 把堆顶存起来# 只要j位置有数while j <= high:# 左孩子和右孩子比较大小 右孩子有没有越界 且 右孩子比左孩子小if j + 1 <= high and li[j + 1] < li[j]:j = j + 1 # 把j指向右孩子# 比较堆顶的tmp和j左右孩子大小比较if li[j] < tmp: # 如果孩子比堆顶小li[i] = li[j] # 把孩子大的换到上面父节点# 往下看一层,将i移动到孩子位置,将j继续向下移动到新i的孩子的位置i = jj = 2 * i + 1else: # tmp更大,把tmp放到i的位置上 结束循环li[i] = tmp # 把tmp放到某一级领导位置上break# 越界了else:li[i] = tmp # 说明i走到最下面一层了,j到还要下一层没有叶子节点位置,是空的,就把tmp放到叶子节点上
建堆的时间复杂度是 O(n) ;向下调整的时间复杂度是 O(log(n))。
2. 堆的插入(offer)
堆的插入总共需要两个步骤:
- 先将元素放入到底层空间中(注意:空间不够时需要扩容)
- 将最后新插入的节点向上调整,直到满足堆的性质 ;

3. 堆的删除(poll)
具体如下:( 注意:堆的删除一定删除的是堆顶元素。)
- 将堆顶元素对堆中最后一个元素交换;
- 将堆中有效数据个数减少一个;
- 对堆顶元素进行向下调整;
代码待补充…
三. 堆的应用
1. 堆排序(从小到大排)
一个数组根据从小到大排序,要创建大堆来排;一个数组根据从大到小排序,要创建小堆来排。
此处就以创建大堆为例。首先将堆顶的元素和堆中的最后一个元素交换,交换后再向下调整,调整后再与堆的倒数第二个元素进行交换。
def sift(li, low, high):"""向下调整的一次过程:param li: 列表:param low: 堆的根节点位置:param high: 堆的最后一个元素的位置:return:"""i = low # 最开始指向根节点j = 2 * i + 1 # 开始是左孩子tmp = li[low] # 把堆顶存起来# 只要j位置有数while j <= high:# 左孩子和右孩子比较大小 右孩子有没有越界 且 右孩子比左孩子大if j + 1 <= high and li[j + 1] > li[j]:j = j + 1 # 把j指向右孩子# 比较堆顶的tmp和j左右孩子大小比较if li[j] > tmp: # 如果孩子比堆顶大li[i] = li[j] # 把孩子大的换到上面父节点# 往下看一层,将i移动到孩子位置,将j继续向下移动到新i的孩子的位置i = jj = 2 * i + 1else: # tmp更大,把tmp放到i的位置上 结束循环li[i] = tmp # 把tmp放到某一级领导位置上break# 越界了else:li[i] = tmp # 说明i走到最下面一层了,j到还要下一层没有叶子节点位置,是空的,就把tmp放到叶子节点上# 堆排序过程
def heap_sort(li):"""1. 先建堆 从最后一个子堆开始,小堆到大堆 依次到根节点2. 向下调整 得到堆顶元素,为最大元素3. 挨个出数 堆顶最大元素和堆最后一个元素交换位置4. 重复2-3,直到堆变空:param li:待排序的列表:return:"""print("开始建大根堆")# n 列表长度n = len(li)# 遍历范围 首先求列表最后一个父元素,最后一个小堆,最后一个子元素下标是n - 1,父下标((n-1)-1))//2,通过左右孩子公式都一样的结果# 最后一个父元素开始,最后-1步长是倒着遍历到列表最后一个元素 找到堆顶0(中间-1,步长负数,-1+1=0),倒序遍历for i in range((n - 2) // 2, -1, -1):sift(li, i, n - 1)# for循环结束,建堆完成了# 挨个出数for i in range(n - 1, -1, -1): # 倒序 i从最后开始# i指向当前堆的最后一个元素li[0], li[i] = li[i], li[0]# 由于是倒序,挨个出数后,尾部有序区指针high,每次左移一位sift(li, 0, i - 1) # i-1是新的highli = [9, 6, 3, 5, 7, 2, 1, 8, 4]print(li)
heap_sort(li)
print(li)
2. top-k问题
若要从N个数字中取得最小的K个数字,则需要创建大小为K的大堆来获取。若要从N个数字中取得最大的K个数字,则需要创建大小为K的小堆来获取。
def sift(li, low, high):"""向上调整的一次过程:param li: 列表:param low: 堆的根节点位置:param high: 堆的最后一个元素的位置:return:"""i = low # 最开始指向根节点j = 2 * i + 1 # 开始是左孩子tmp = li[low] # 把堆顶存起来# 只要j位置有数while j <= high:# 左孩子和右孩子比较大小 右孩子有没有越界 且 右孩子比左孩子小if j + 1 <= high and li[j + 1] < li[j]:j = j + 1 # 把j指向右孩子# 比较堆顶的tmp和j左右孩子大小比较if li[j] < tmp: # 如果孩子比堆顶小li[i] = li[j] # 把孩子大的换到上面父节点# 往下看一层,将i移动到孩子位置,将j继续向下移动到新i的孩子的位置i = jj = 2 * i + 1else: # tmp更大,把tmp放到i的位置上 结束循环li[i] = tmp # 把tmp放到某一级领导位置上break# 越界了else:li[i] = tmp # 说明i走到最下面一层了,j到还要下一层没有叶子节点位置,是空的,就把tmp放到叶子节点上def topk(li, k):# 先取列表前k个元素heap = li[0:k]# 1. 建小根堆for i in range((k - 2) // 2, -1, -1):sift(heap, i, k - 1)print("*" * 80)print("小根堆heap建堆完成,", heap)print("*" * 80)# 2. 遍历 li列表里k后面剩下的元素for i in range(k, len(li)):# 依次拿k后面的值和小根堆 堆顶的值比较大小if li[i] > heap[0]: # 如果值 大于 堆顶元素值heap[0] = li[i] # 把大的值 放到堆顶sift(heap, 0, k - 1)# 3. 挨个出数for i in range(k - 1, -1, -1): # 倒序 i从最后开始# i指向当前堆的最后一个元素heap[0], heap[i] = heap[i], heap[0]sift(heap, 0, i - 1) # i-1是新的highreturn heapli = [i for i in range(20)]
random.shuffle(li)print(li)
print(topk(li, 10))
print(li)
相关文章:
数据结构--堆
一. 堆 1. 堆的概念 堆(heap):一种有特殊用途的数据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中查找最值。 堆在物理层面上,表现为一组连续的数组区间:long[] array &…...
Android12之报错 error: BUILD_COPY_HEADERS is obsolete(一百六十七)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...
vue前端中v-model与ref的区别
v-model <template><input type"text" v-model"message"> </template>作用:将输入框与message绑定,及将用户输入的内容绑定到message这个变量上,但是message是无法在script中获取到的,要想…...
探索未来:硬件架构之路
文章目录 🌟 硬件架构🍊 基本概念🍊 设计原则🍊 应用场景🍊 结论 📕我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作…...
Linux 系统安装 Redis7 —— 超详细操作演示!
内存数据库 Redis7 一、Redis 概述1.1 Redis 简介1.2 Redis 的用途1.3 Redis 特性1.4 Redis 的IO模型 二、Redis 的安装与配置2.1 Redis 的安装2.2 连接前的配置2.3 Redis 客户端分类2.4 Redis 配置文件详解 三、Redis 命令四、Redis 持久化五、Redis 主从集群六、Redis 分布式…...
首次建站用香港服务器有影响没?
对于首次租用香港服务器的朋友来说,难免会对它没有一个很清晰的认知。因此,本文就从香港服务器适用人群,以及建站影响,选择技巧上做一个全方位的解答。 1. 哪一类人群适合使用香港服务器建站? 做外贸业务的网站。香港走的国…...
大数据Flink(九十八):SQL函数的归类和引用方式
文章目录 SQL函数的归类和引用方式 一、SQL 函数的归类...
Python文件共享+cpolar内网穿透:轻松实现公网访问
文章目录 1.前言2.本地文件服务器搭建2.1.Python的安装和设置2.2.cpolar的安装和注册 3.本地文件服务器的发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 数据共享作为和连接作为互联网的基础应用,不仅在商业和办公场景有广泛的应用&#…...
Flink之源算子Data Source
源算子Data Source 概述内置Data Source基于集合构建基于文件构建基于Socket构建 自定义Data SourceSourceFunctionRichSourceFunction 常见连接器第三方系统连接器File Source连接器DataGen Source连接器Kafka Source连接器RabbitMQ Source连接器MongoDB Source连接器 概述 Fl…...
在雷电模拟器9上安装magisk并安装LSPosed模块以及其Manager管理器(一)
环境:win10 64,雷电模拟器9.0.60(9),Android 9。 之前我都是用雷电模拟器版本4.0.78,Android版本7.1.2,为什么本篇要使用9了呢?先解答下这个问题。原因如下:经过我的测试,LSPosed不支…...
Apache atlas 元数据管理治理平台使用和架构
1、前言 Apache Atlas 是托管于 Apache 旗下的一款元数据管理和治理的产品,目前在大数据领域应用颇为广泛,可以很好的帮助企业管理数据资产,并对这些资产进行分类和治理,为数据分析,数据治理提供高质量的元数据信息。…...
MFF论文笔记
论文名称:Improving Pixel-based MIM by Reducing Wasted Modeling Capability_发表时间:ICCV2023 作者及组织:上海人工智能实验室,西门菲沙大学,香港中文大学 问题与贡献 MIM(Model Maksed Model)方法可以分为两部分…...
Leetcode 02.07 链表相交(链表)
Leetcode 02.07 链表相交(链表) 解法1 尾部对齐解法2:太厉害了,数学归纳推导的方法 很巧妙,这就是将链表的尾端对齐后再一起遍历,这样能满足题目的要求。因为相交之后两个链表到结束的所有节点都一样了&…...
Bootstrap的媒体对象组件(图文展示组件),挺有用的一个组件。
Bootstrap的.media类是用于创建媒体对象的,媒体对象通常用于展示图像(图片)和文本内容的组合,这种布局在展示新闻文章、博客帖子等方面非常常见。.media类使得创建这样的媒体对象非常简单,通常包含一个图像和相关的文本…...
Day2力扣打卡
打卡记录 无限数组的最短子数组(滑动窗口) 链接 思路:先求单个数组的总和,再对两个重复数组所组成的新数组上使用 不定长的滑动窗口 来求得满足目标的最小长度。 class Solution { public:int minSizeSubarray(vector<int>…...
项目经理每天,每周,每月的工作清单
很多不懂项目管理的伙伴问,项目经理每天每周每个月的工作是什么呢? 仿佛他们什么都管,但是又没有具体的产出,但是每天看他们比谁都忙,其实很简单,项目中的每个环节负责具体的事情,但是每个环节…...
Java —— 运算符
目录 1. 什么是运算符 2. 算术运算符 2.1 基本四则运算符: 加减乘除模( - * / %) 2.2 增量运算符 - * %与 自增/自减运算符 -- 3. 关系运算符 4. 逻辑运算符 4.1 逻辑与 && 4.2 逻辑或|| 4.3 逻辑非 ! 4.4 短路求值 5. 位运算符 5.1 按位与 & 5.2 按位或 5.3 按位…...
【C++ 中的友元函数:解密其神秘面纱】
友元函数,作为C中一个重要但常常被误解的概念,经常让初学者感到困惑。本文将带您逐步了解友元函数的含义、用途以及如何正确使用它们。 什么是友元函数? 在C中,友元函数是一种特殊的函数,它允许某个类或类的成员函数…...
YOLOv8涨点技巧:手把手教程,注意力机制如何在不同数据集上实现涨点的工作,内涵多种网络改进方法
💡💡💡本文独家改进:手把手教程,解决注意力机制引入到YOLOv8在自己数据集不涨点的问题点,本文提供五种改进方法来解决此问题; ContextAggregation | 亲测在血细胞检测项目中涨点,…...
牛客:FZ12 牛牛的顺时针遍历
FZ12 牛牛的顺时针遍历 文章目录 FZ12 牛牛的顺时针遍历题目描述题解思路题解代码 题目描述 题解思路 通过一个变量来记录当前方向,遍历矩阵,每次遍历一条边,将该边的信息加入到结果中 题解代码 func spiralOrder(matrix [][]int) []int {…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
