Python - 深夜数据结构与算法之 Heap Binary Heap

目录

一.引言

二.堆与二叉堆介绍

1.Heap 堆

2.Binary Heap 二叉堆

3.HeapifyUp 添加节点

4.HeapifyDown 删除节点

5.Heap 时间复杂度

6.Insert & Delete 代码实现

三.经典算法实战

1.Smallest-K [M14]

2.Sliding-Window-Max [239]

3.Ugly-Number [264]

4.Top-K-Freq-Ele [347]

四.总结


一.引言

前面介绍了树和二叉树的概念,接下来介绍 Heap 堆和 Binary Heap 二叉堆,这里堆是一个抽象的数据结构,二叉堆只是其实现的一种形式,并不代表所有堆都使用二叉树实现。

二.堆与二叉堆介绍

1.Heap 堆

堆主要分为两种表现形式,最大堆 or 最小堆,也有叫大根堆 or 小根堆的,其可以在常数时间 o(1) 获取到最大 or 最小的元素,其一般包含 3 个基础 API:

◆ find-max - 寻找堆中的最大值

◆ delete-max - 删除堆中的最大值

◆ insert - 向堆中插入一个新元素

注意 📢 delete 和 insert 操作都会导致堆的结构破坏,所以需要重新调整父子节点位置从而维护堆的原始性质。

2.Binary Heap 二叉堆

二叉堆是基于二叉树实现的 Heap,不过有一点需要注意这里的二叉树是完全二叉树,假设其深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。也就是除了最后一层叶子 🍃 节点外,其余层的节点都是满的且没有 None。 同时二叉堆一般基于 List 实现,以下述列表为例:

[110, 100, 90, 40, 80, 20, 60, 10, 30, 50, 70]

完全二叉树可以看作是满二叉树从尾部叶子节点开始清退,即数组尾部清退,剩下的都是满足完全二叉树的,这样做主要是为了提高效率,如果过多节点为 None,二叉树的深度 h 增加,那么搜索的时间复杂度也会逐渐增加且浪费空间。 基于完全二叉树形态,其具备以下性质:

通过索引 i 我们可以获取对应索引的左右孩子节点和父节点的位置 [如果有的话],假设是 k 叉树的话,索引 i 的 k 个孩子索引为 k*i + 1、k*i + 2、k*i+3 以此类推,父节点索引则为 floor((i-1)/k)。 

3.HeapifyUp 添加节点

基于二叉堆 index 及其父子节点的关系,insert 操作首先将节点插入数组尾部,然后依次与父节点比较向上移动直到无法移动,增加元素结束。

◆ 插入 85 到 Heap

◆ 元素添加到队尾 

◆ 持续向上移动并交换

4.HeapifyDown 删除节点

删除元素首先将 root 节点移出,随后将尾节点替换至 root,与左右节点中较大的节点替换位置,如此循环下去,直到不能移动并恢复堆的性质。 

5.Heap 时间复杂度

再次重申下堆是一个抽象的结构,其有多种实现方式,就像是接口一样。这里最常见的实现方法为二叉堆,而随着实现方法的复杂,其 insert 和 delete 的复杂度也会更加友好。这里 Binaey Heap 的 find Min/Max 的时间复杂度为 o(1),其余时间复杂度为 o(logn)。

6.Insert & Delete 代码实现

◆ HeapifyUp

这里用了指针的方式,不断将较小的 Parent 节点与 Insert 节点互换,最终停止循环。

◆ HeapifyDown

增加元素只需要与 Parent 节点比较,但是删除节点需要和左右子节点比较,所以需要使用 maxChild 函数寻找更大的节点进行交换,这样才能满足堆得性质。 

三.经典算法实战

1.Smallest-K [M14]

最小的 K 个数: https://leetcode.cn/problems/smallest-k-lcci/

题目分析

既然是在堆的讲解下,所以我们可以直接使用 Heap,由于是最小的 k 个数,所以可以使用最小堆,每次返回堆顶元素并更新堆,执行 k 次即可。

小根堆

class Solution(object):def smallestK(self, arr, k):""":type arr: List[int]:type k: int:rtype: List[int]"""heapq.heapify(arr)res = []for i in range(k):res.append(heapq.heappop(arr))return res

python 的 heapq 默认为最小堆,直接将元素通过 o(n) 复杂度构建一个 Heap,随后 pop k 次堆顶的元素即可。当然 heapq 提供 nsmallest 和 nlarggest (k, nums) 的函数可以直接获取前 k 个最小、最大的元素,其等价于 nums.sort()[:k]。

2.Sliding-Window-Max [239]

最大滑动窗口: https://leetcode.cn/problems/sliding-window-maximum/description/

题目分析

之前 ArrayList 部门我们通过 dequeue 双端队列实现了 k-window-max 的算法,一个技巧就是我们需要记录每个元素的索引,从而判断是否在窗口中。这里 python 默认的 heapq 为小根堆,通过 -1 * nums 转换为大根堆,随后遍历 k 窗口,取出堆顶最大值即可,注意在堆的维护过程中,排除索引 index 在 k-window 之外的元素。

大根堆

class Solution(object):def maxSlidingWindow(self, nums, k):n = len(nums)# 构造最大堆q = [(-nums[i], i) for i in range(k)]heapq.heapify(q)# 先获取前k个元素的 maxans = [-q[0][0]]for i in range(k, n):# 向堆添加新的元素heapq.heappush(q, (-nums[i], i))# 堆顶为窗口外元素则去除该元素while q[0][1] <= i - k:heapq.heappop(q)# pop 后仍然保持堆的结构,弹出堆顶最大值ans.append(-q[0][0])return ans

对于最大、最小 k 个数的题目,我们都可以想到通过 heap 来实现,工程环境下直接使用对应的库即可。

3.Ugly-Number [264]

丑数: https://leetcode.cn/problems/ugly-number-ii/description/

题目分析 

这个题目主要是理解比较困难,感觉是记忆性题目。1 是丑数,假设 x 是丑数,则 2x、3x、5x 也是丑数,所以我们依次加入堆中,第 n 次出队时,对应元素即为第 n 个丑数。

最小堆

#!/usr/bin/python
# -*- coding: UTF-8 -*-class Solution(object):def nthUglyNumber(self, n):""":type n: int:rtype: int"""nums = [2, 3, 5]# 去重appeared = {1}# 将初始化丑数放到队列里pq = [1]# 每次从队列取最小值,然后将对应数 2x 3x 5x 入队for i in range(1, n+1):x = heapq.heappop(pq)if i == n:return xfor num in nums:t = num * xif t not in appeared:appeared.add(t)heapq.heappush(pq, t)return -1

三指针

class Solution(object):def nthUglyNumber(self, n):if n < 0:return 0dp = [1] * n# 维护3个指针index2, index3, index5 = 0, 0, 0for i in range(1, n):# 每次看哪个位置乘出来的丑数最小dp[i] = min(2 * dp[index2], 3 * dp[index3], 5 * dp[index5])# 用过的索引 += 1if dp[i] == 2 * dp[index2]: index2 += 1if dp[i] == 3 * dp[index3]: index3 += 1if dp[i] == 5 * dp[index5]: index5 += 1return dp[n - 1]

4.Top-K-Freq-Ele [347]

前 k 个高频元素: https://leetcode.cn/problems/top-k-frequent-elements/description/

题目分析 

这题找 k 个最大,还是使用堆即可,只不过需要预先做一次 word_count 统计词频,这里如果是 scala 可以直接用 tuple-2 再 sortBy(-_.2) 即可,python 的话就直接使用 heqpq.nlargest 即可。

最大堆 API

class Solution(object):def topKFrequent(self, nums, k):""":type nums: List[int]:type k: int:rtype: List[int]"""if k == 0:return []# 1.构造 word count 计数count = {}for i in nums:if i not in count:count[i] = 0count[i] += 1# 2.构造 Heapheap = [(val, key) for key, val in count.items()]# 3.取前 k 个元素return [item[1] for item in heapq.nlargest(k, heap)]

这里使用 nlargest 获取前 k 个元素,但是我们构造时使用了全部数据,下面我们自己维护 k 个元素实现 heap。

K-最大堆

class Solution(object):def topKFrequent(self, nums, k):""":type nums: List[int]:type k: int:rtype: List[int]"""if k == 0:return []# 1.构造 word count 计数count = {}for i in nums:if i not in count:count[i] = 0count[i] += 1# 2.构造 Heapre = []heapq.heapify(re)for key, val in count.items():# 添加元素heapq.heappush(re, (val, key))# 超过 k 就把堆顶最小的拿走,最后剩下 K 个最大的while len(re) > k:heapq.heappop(re)return [i[1] for i in re]

 因为我们只维护了 k 大小的堆,遍历数组是 o(n),而堆内排序是 o(logk),所以时间快一些。

四.总结

本文结合上一篇文章的二叉树介绍了堆的概念以及常用的堆的功能应用,取前 k 个最大 or 最小的问题适合用堆实现,其次常用的二叉堆我们应该记住父子节点之间的关系。最后是常用堆的实现代码,这里我们都是调用 heapq 库,如果想要自己了解可以参考下述链接: 

Heap Sort – Data Structures and Algorithms Tutorials

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/298163.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工具系列:TensorFlow决策森林_(2)排序学习Learning to Rank

文章目录 安装 TensorFlow Decision Forests导入库什么是排序模型&#xff1f;让我们训练一个排序模型使用排序模型进行预测 欢迎来到 TensorFlow决策森林&#xff08; TF-DF&#xff09;的 学习排序Learning to Rank。 在本文中&#xff0c;您将学习如何使用 TF-DF进行排序…

股市中的Santa Claus Rally (圣诞节行情)

圣诞节行情 Santa Claus Rally Santa Claus Rally 是指 12 月 25 日圣诞节前后股市的持续上涨这样一个现象。《股票交易员年鉴》的创始人 Yale Hirsch 于 1972 年创造了这个定义&#xff0c;他将当年最后五个交易日和次年前两个交易日的时间范围定义为反弹日期。 根据 CFRA Re…

Redis基础-Redis概念及常见命令

1.nosql数据库 NoSQL数据库是一种提供了非关系型数据存储的数据库系统&#xff0c;与传统的关系型数据库&#xff08;如SQL数据库&#xff09;不同。NoSQL数据库的特点是灵活性高&#xff0c;能够处理结构化、半结构化或非结构化数据。它们通常用于大数据和实时Web应用。NoSQL数…

Web Components入门不完全指北

目前流行的各类前端框架&#xff0c;不管是react, angular还是vue&#xff0c;都有一个共同点&#xff0c;那就是支持组件化开发&#xff0c;但事实上随着浏览器的发展&#xff0c;现在浏览器也原生支持组件式开发&#xff0c;本文将通过介绍Web Components 的三个主要概念&…

JavaScript常用技巧专题五

文章目录 一、使用适当的命名和注释来提高代码可读性二、优雅的写条件判断代码2.1、普通的if else2.2、三元运算符2.3、多个if else2.4、switch case2.5、对象写法2.6、Map写法 三、封装条件语句四、函数应该只做一件事五、Object.assign给默认对象赋默认值六、函数参数两个以下…

SQL进阶理论篇(二十一):基于SQLMap的自动化SQL注入

文章目录 简介获取当前数据库和用户信息获取MySQL中的所有数据库名称查询wucai数据库中的所有数据表查看heros数据表中的所有字段查询heros表中的英雄信息总结参考文献 简介 从上一小节&#xff0c;可以发现&#xff0c;如果我们编写的代码存在着SQL注入的漏洞&#xff0c;后果…

网络7层架构

网络 7 层架构 什么是OSI七层模型&#xff1f; OSI模型用于定义并理解数据从一台计算机转移到另一台计算机&#xff0c;在最基本的形式中&#xff0c;两台计算机通过网线和连接器相互连接&#xff0c;在网卡的帮助下共享数据&#xff0c;形成一个网络&#xff0c;但是一台计算…

Vue 3 Composition API:让组件开发更高效、灵活(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【JavaEE初阶一】线程的概念与简单创建

1. 认识线程&#xff08;Thread&#xff09; 1.1 关于线程 1.1.1 线程是什么 由前一节的内容可知&#xff0c;进程在进行频繁的创建和销毁的时候&#xff0c;开销比较大&#xff08;主要体现在资源的申请和释放上&#xff09;&#xff0c;线程就是为了解决上述产生的问题而提…

聪明高效能力广,AGI如何赋能内容管理?

文 | 智能相对论 作者 | 叶远风 毫无疑问&#xff0c;现在的大模型在技术比拼之外&#xff0c;如何通过产品化的方式走入到实际业务&#xff0c;是各厂商的着力点。 而一些一贯与数字化场景紧密融合的服务厂商&#xff0c;在大模型浪潮一开始就已经走在落地一线。 大数据基…

【Linux系统基础】(3)在Linux上部署运维监控Zabbix和Grafana

目录 运维监控Zabbix部署简介安装安装前准备 - Mysql安装Zabbix Server 和 Zabbix Agenta. 安装Zabbix yum库b. 安装Zabbix Server、前端、Agentc. 初始化Mysql数据库d. 为Zabbix Server配置数据库e. 配置Zabbix的PHP前端 配置zabbix 前端&#xff08;WEB UI&#xff09; 运维监…

关于Python里xlwings库对Excel表格的操作(十七)

这篇小笔记主要记录如何【获取和设置单元格行高、列宽】。 前面的小笔记已整理成目录&#xff0c;可点链接去目录寻找所需更方便。 【目录部分内容如下】【点击此处可进入目录】 &#xff08;1&#xff09;如何安装导入xlwings库&#xff1b; &#xff08;2&#xff09;如何在W…