堆 (优先队列)

堆 (优先队列)

定义

堆是一种特殊的完全二叉树,其中每个节点的键值都满足某种顺序属性:

  • 大顶堆(Max Heap):对于任意节点i(除了根节点),其值都大于或等于其子节点的值。换句话说,每个父节点的值都大于或等于其子节点的值。
  • 小顶堆(Min Heap):对于任意节点i(除了根节点),其值都小于或等于其子节点的值。换句话说,每个父节点的值都小于或等于其子节点的值。

堆通常用数组来实现,因为数组可以高效地通过索引访问节点,而不需要存储指向父节点或子节点的指针。在数组实现中,对于任意节点i(数组索引从0开始):

  • 其左子节点的索引是 2*i + 1
  • 其右子节点的索引是 2*i + 2
  • 其父节点的索引是 (i-1)/2(向下取整)

堆支持的主要操作包括插入(将新元素添加到堆中并重新调整以保持堆的性质)、删除(移除根节点并重新调整堆)以及查找最大(或最小)元素(在大顶堆或小顶堆中,根节点总是最大或最小的)。

计算机数据结构中的“堆”和计算机内存管理中的“堆”是两个不同的概念:

  1. 数据结构中的堆(Heap):
  • 这是一种特殊的树形数据结构,通常用于实现优先队列。在堆中,任意节点的值总是不大于(或不小于)其子节点的值(大顶堆或小顶堆)。
  • 堆可以非常高效地支持两种主要操作:插入元素和提取最大(或最小)元素。
  • 堆通常有两种实现方式:数组和链表。在数组实现中,堆可以通过数组索引关系快速定位父节点和子节点。
  1. 内存管理中的堆(Memory Heap):
  • 这是操作系统用来动态分配内存的区域。程序可以在运行时请求一定量的内存,并在使用完毕后释放。
  • 堆内存不是预先分配的,而是根据需要动态分配的。这与栈内存不同,栈内存是在程序运行前就已经分配好的。
  • 堆内存管理涉及到复杂的算法,如内存分配器(如ptmalloc, jemalloc等)和垃圾回收机制(在某些语言中,如Java和C#)。

基本操作&实现

def heappush(heap, item):"""Push item onto heap, maintaining the heap invariant."""heap.append(item)_siftdown(heap, 0, len(heap)-1)def heappop(heap):"""Pop the smallest item off the heap, maintaining the heap invariant."""lastelt = heap.pop()    # raises appropriate IndexError if heap is emptyif heap:returnitem = heap[0]heap[0] = lastelt_siftup(heap, 0)return returnitemreturn lasteltdef heapify(x):"""Transform list into a heap, in-place, in O(len(x)) time."""n = len(x)# Transform bottom-up.  The largest index there's any point to looking at# is the largest with a child index in-range, so must have 2*i + 1 < n,# or i < (n-1)/2.  If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so# j-1 is the largest, which is n//2 - 1.  If n is odd = 2*j+1, this is# (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1.for i in reversed(range(n//2)):_siftup(x, i)def _siftdown(heap, startpos, pos):newitem = heap[pos]# Follow the path to the root, moving parents down until finding a place# newitem fits.while pos > startpos:parentpos = (pos - 1) >> 1parent = heap[parentpos]if newitem < parent:heap[pos] = parentpos = parentposcontinuebreakheap[pos] = newitemdef _siftup(heap, pos):endpos = len(heap)startpos = posnewitem = heap[pos]# Bubble up the smaller child until hitting a leaf.childpos = 2*pos + 1    # leftmost child positionwhile childpos < endpos:# Set childpos to index of smaller child.rightpos = childpos + 1if rightpos < endpos and not heap[childpos] < heap[rightpos]:childpos = rightpos# Move the smaller child up.heap[pos] = heap[childpos]pos = childposchildpos = 2*pos + 1# The leaf at pos is empty now.  Put newitem there, and bubble it up# to its final resting place (by sifting its parents down).heap[pos] = newitem_siftdown(heap, startpos, pos)

python中对应的库

Python 的 heapq 模块提供了一个基于列表的堆队列算法的实现,也称为优先队列。以下是 heapq 模块的一些常用方法和注意事项:

常用方法:

  1. heappush(heap, item)

    • 将元素 item 添加到堆 heap 中。
    • 堆是递增的,即父节点总是小于或等于其子节点(小顶堆)。
  2. heappop(heap)

    • 移除并返回堆 heap 中的最小元素。
    • 这个操作保持了堆的性质。
  3. heapify(x)

    • 将列表 x 转换成堆。
    • 转换后的列表将满足堆的性质,即父节点小于子节点
  4. heapreplace(heap, item)

    • 移除堆 heap 中的最小元素,并返回它,然后添加 item 到堆中。
    • 这个方法在保持堆的性质的同时,实现了元素的替换。
  5. nlargest(n, iterable, key=None)

    • 返回 iterable 中的 n 个最大元素。
    • 可以指定 key 函数来确定元素的比较方式。
  6. nsmallest(n, iterable, key=None)

    • 返回 iterable 中的 n 个最小元素。
    • 可以指定 key 函数来确定元素的比较方式。

注意事项:

  1. 堆的性质

    • 默认情况下,heapq 实现的是小顶堆,即父节点的值小于子节点的值。
    • 要实现大顶堆,可以在添加和移除元素时对元素的值取负。
  2. 列表作为堆

    • heapq 使用列表来实现堆,因此列表的索引可以用来定位父节点和子节点。
    • 父节点 i 的左子节点索引是 2*i + 1,右子节点索引是 2*i + 2,父节点索引是 (i-1)//2
  3. 效率

    • heappushheappop 操作的时间复杂度是 O(log n)。
    • heapify 操作的时间复杂度是 O(n),适用于将一个列表转换为堆。
  4. 稳定性

    • heapq 不保证元素的顺序,即使元素具有相同的值,它们在堆中的顺序也可能不同。
  5. 内存使用

    • 由于 heapq 使用列表实现,大量元素可能会占用较多的内存。
  6. 线程安全

    • heapq 操作不是线程安全的,如果需要在多线程环境中使用,需要外部同步。
  7. 错误处理

    • 如果尝试从空堆中弹出元素,heapq 会抛出 IndexError
    • 如果尝试在空堆上执行 heappopheapreplace,应该先检查堆是否为空。
  8. Python版本
    从Python 3开始,heapq模块的函数(包括heappushheappopheapify)都支持一个key参数,允许你指定一个函数,该函数会被用来从列表中的每个元素中提取一个用于比较的键。这个键用于确定元素在堆中的顺序。

下面是一个使用key参数来指定排序键的例子:

import heapq# 假设我们有一个列表,其中包含一些元组,我们想根据元组的第二个元素来建立堆
data = [(1, 'apple'), (3, 'pear'), (2, 'orange'), (5, 'banana'), (4, 'grape')]# 使用heapq.heapify将列表转换为堆,并指定排序键为元组的第二个元素
heapq.heapify(data, key=lambda x: x[1])# 现在,我们可以使用heapq.heappop来获取最小元素
print(heapq.heappop(data))  # 输出: (3, 'pear'),因为'pear'是所有水果中字典序最小的

常见题型

215数组中第k大元素

法一 暴力解法

对于所有元素排序后返回对应位置的值
时间复杂度O(nlogn),空间复杂度O(logn)

法二 使用堆 (注意如果要手写堆如何实现 todo)

对于每个元素取负之后用heapq中函数构建堆,并弹出k次最小元素,最后一次的结果的负数就是解

import heapq
class Solution:def findKthLargest(self, nums: List[int], k: int) -> int:t = [-n for n in nums]heapq.heapify(t)ans = 0 for _ in range(k):ans = heapq.heappop(t)return -ans

时间复杂度:O(nlogn),建堆的时间代价是 O(n),删除的总代价是 O(klogn),因为 k<n,故渐进时间复杂为 O(n+klogn)=O(nlogn)。

空间复杂度:O(logn),即递归使用栈空间的空间代价。

法三 基于快速排序的选择算法

快排过程时每次选择一个基准,然后将所有比基准小的放在基准前,所有大于等于基准的放在基准后面,然后对于左右子序列重复这个过程直到左右子序列都是空,最终的序列就是有序的

对于本题,媒体确定基准排序后的位置,和第k大的数排序后的位置对比,确定直接返回还是继续对于左或右重复刚才的操作,从而可以减少每次遍历的范围。

class Solution:def findKthLargest(self, nums: List[int], k: int) -> int:length = len(nums)target = length - k def partition(j, left, right):cur = leftfor i in range(left, right):if nums[i]<nums[j]:nums[i], nums[cur] = nums[cur], nums[i]cur += 1nums[cur], nums[j] = nums[j], nums[cur]return cur left, right = 0, length-1n = rightwhile True:t = partition(n, left, right)if t==target:return nums[target]elif t<target:left = t+1n = rightelse:right = t-1n = right

上面的代码在有大量重复1的测试用例中无法通过。

题解

改进后的解法

import random
class Solution:def findKthLargest(self, nums: List[int], k: int) -> int:def partition(nums, left, right):rn = random.randint(left, right)nums[left], nums[rn] = nums[rn], nums[left]pivot = nums[left]le, ge = left+1, rightwhile True:while le<=ge and nums[le]<pivot:le += 1while le<=ge and nums[ge]>pivot:ge -= 1if le>=ge:breaknums[le], nums[ge] = nums[ge], nums[le]le += 1ge -= 1nums[left], nums[ge] = nums[ge], nums[left]return gelength = len(nums)target = length - k left, right  = 0, length-1while True:pivot = partition(nums, left, right)if pivot==target:return nums[pivot]elif pivot<target:left = pivot+1else:right = pivot-1

时间复杂度O(n),空间复杂度O(1)

快排讲解&二路和三路排序过程 (超级推荐)
https://www.bilibili.com/video/BV19S4y187Rt?spm_id_from=333.788.recommend_more_video.0&vd_source=9c14f0916f2233888f50267fd1553056

502 IPO

因为利润都是非负的,所以每次都选择 当前可以选择的项目中利润最大的项目。
当前利润最大可以通过堆实现,由于每完成一个项目的当前总资本就会变大,可以选择的项目可能会变多,注意到每个项目只能选择一次,如果没完成一个项目就要对剩下的项目遍历更新堆,元素会被重复访问多次,
所以可以先对于成本和利润进行排序,从而更新堆的过程中剩下的项目只需要遍历一次,代码如下

import heapq
class Solution:def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:temp = [(capital[i],-profits[i]) for i in range(len(profits))]temp.sort()cur = 0 t = []heapq.heapify(t)for _ in range(k):while cur<len(capital):if temp[cur][0]<=w:heapq.heappush(t,temp[cur][1])cur += 1else:breakif not t:breakw -= heapq.heappop(t)# w -= a[1]return w

时间复杂度:时间复杂度:O((n+k)logn),其中 n 是数组 profits 和 capital 的长度,k 表示最多的选择数目。我们需要 O(nlogn) 的时间复杂度来来创建和排序项目,往堆中添加元素的时间不超过 O(nlogn),每次从堆中取出最大值并更新资本的时间为 O(klogn),因此总的时间复杂度为 O(nlogn+nlogn+klogn)=O((n+k)logn)

空间复杂度:O(n)

373 寻找和最小的k组数

直接构建所有可能的数组,然后排序后输出最小的k个

存在用例超出时间限制

多路归并的方法 来自宫水三叶

令 nums1 的长度为 n,nums2 的长度为 m,所有的点对数量为 n×m。

其中每个 nums1[i] 参与所组成的点序列为:

[(nums1[0],nums2[0]),(nums1[0],nums2[1]),…,(nums1[0],nums2[m−1])]

[(nums1[1],nums2[0]),(nums1[1],nums2[1]),…,(nums1[1],nums2[m−1])]

[(nums1[n−1],nums2[0]),(nums1[n−1],nums2[1]),…,(nums1[n−1],nums2[m−1])]

由于 nums1 和 nums2 均已按升序排序,因此每个 nums1[i] 参与构成的点序列也为升序排序,这引导我们使用「多路归并」来进行求解。

具体的,起始我们将这 n 个序列的首位元素(点对)以二元组 (i,j) 放入优先队列(小根堆),其中 i 为该点对中 nums1[i] 的下标,j 为该点对中 nums2[j] 的下标,这步操作的复杂度为 O(nlogn)。这里也可以得出一个小优化是:我们始终确保 nums1 为两数组中长度较少的那个,然后通过标识位来记录是否发生过交换,确保答案的点顺序的正确性。

每次从优先队列(堆)中取出堆顶元素(含义为当前未被加入到答案的所有点对中的最小值),加入答案,并将该点对所在序列的下一位(如果有)加入优先队列中。

举个 🌰,首次取出的二元组为 (0,0),即点对 (nums1[0],nums2[0]),取完后将序列的下一位点对 (nums1[0],nums2[1]) 以二元组 (0,1) 形式放入优先队列。

可通过「反证法」证明,每次这样的「取当前,放入下一位」的操作,可以确保当前未被加入答案的所有点对的最小值必然在优先队列(堆)中,即前 k 个出堆的元素必然是所有点对的前 k 小的值。

import heapq
class Solution:def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:ans = []len1, len2 = len(nums1), len(nums2)# 注意,为了降低堆的大小提升运行效率,想要保持nums1是长度更小的,所以不满足的时候nums1和nums2进行了对换,注意到对应的数组长度也对应变了# 但是最终输出的结果需要按照原本nums1和nums2的结果来,所以需要有flag标识放入结果中应该按照什么样的顺序flag = len1>len2if len1>len2:len1, len2, nums1, nums2 = len2,len1, nums2, nums1pq = []for i in range(len1):heapq.heappush(pq, (nums1[i]+nums2[0], i, 0))for _ in range(k):_, a, b = heapq.heappop(pq)ans.append([nums1[a], nums2[b]] if not flag else [nums2[b], nums1[a]])if b+1<len2:heapq.heappush(pq, (nums1[a]+nums2[b+1], a, b+1))return ans

时间复杂度:令 M 为 n、m 和 k 三者中的最小值,复杂度为 O((M+k)logM)
空间复杂度:O(M)

295 数据流中位数

按照中位数的定义只需要获取到排序后中间位置的值,这里有大小和位置的两个限定,如果通过两个堆分别维护当前数和当前数的负数,堆顶分别是对应的最小值和最大值,同时要求两个堆元素个数要相同,不同时只能向后者添加元素,此时通过堆顶就可以获得排序后最中间的两个数,按照总数量的奇偶性输出结果,代码如下

注意到,增加元素的时候,虽然知道最终应该是哪个堆应该多一个元素,但是新元素和现有元素的大小其实没有确定,即真正应该增加到堆中的元素没有确定,所以add中需要先向另一个堆增加元素以找到真正应该增加到另一个堆的元素

class MedianFinder:def __init__(self):self.min_ = []self.max_ = []def addNum(self, num: int) -> None:if len(self.min_)!=len(self.max_):heappush(self.min_, -num)heappush(self.max_, -heappop(self.min_))else:heappush(self.max_, num)heappush(self.min_, -heappop(self.max_))def findMedian(self) -> float:if len(self.min_)!=len(self.max_):return -self.min_[0]else:return (self.max_[0]-self.min_[0])/2

当前元素个数n,则增加一个数字时间复杂度是O(logn),找到中位数时间复杂度是O(1)

空间复杂度是O(n)

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【精选】AI Coding 新范式:Windsurf、Cursor、Coze齐上阵

2AGI.NET | 探索 AI 无限潜力&#xff0c;2AGI 为您带来最前沿资讯。 随着人工智能技术的飞速发展&#xff0c;AI Coding领域迎来了前所未有的变革。Codeium的Windsurf、Cursor的agent模式更新、Copilot的新版本以及Coze的AI应用能力&#xff0c;都在推动着编程领域的创新。本期…

Free-RTOS实现LED闪烁

开发板&#xff1a;正点原子探索者 F407 LED定时定时闪烁 本次实验验证&#xff1a; 配置文件 1、打开CubeMX 2、选择芯片型号&#xff0c;然后点击开始项目 3、配置时钟 配置烧录引脚&#xff0c;与FreeRTOS系统时钟 选择FreeRTOS 这里已经默认有一个任务&#xff…

java+ssm+mysql水产品商城

项目介绍&#xff1a; 使用javassmmysql开发的水产品商城&#xff0c;系统包含管理员、用户角色&#xff0c;功能如下&#xff1a; 管理员&#xff1a;用户管理&#xff1b;种类管理&#xff1b;商品管理&#xff1b;订单管理&#xff1b;评论管理&#xff1b;新闻管理&#…

SYN6288语音合成模块使用说明(MicroPython、STM32、Arduino)

模块介绍 SYN6288中文语音合成模块是北京宇音天下科技有限公司推出的语音合成模块。该模块通过串口接收主控传来的语音编码后&#xff0c;可自动进行自然流畅的中文语音播报。 注&#xff1a;SYN6288模块无法播报英文单词和句子&#xff0c;只能按字母播报英文 &#xff1b;而…

Windows设备go环境安装配置

一、下载go安装包 官网链接&#xff1a;All releases - The Go Programming Language (google.cn) 安装过程比较简单&#xff0c;这里不再赘述&#xff0c;可参考这位博主的文章。本文重点在环境配置。golang环境详细安装、配置_golang安装-CSDN博客 二、环境变量配置 1.添…

vulnhub靶场【hacksudo】之aliens

前言 靶机&#xff1a;hacksudo-aliens 攻击&#xff1a;kali 都是采用虚拟机的形式&#xff0c;网卡桥接模式 主机发现 使用arp-scan -l或者netdiscover -r 192.168.1.1/24进行探索 信息收集 使用nmap扫描 两个http服务&#xff0c;一个ssh服务 网站信息 访问查看 访…

(数据结构与算法)递归 递归是什么 递归的案例和场景 递归进阶

递归的定义和应用条件 递归就是程序调用自身的编程技巧&#xff1b; 把大型复杂的问题转化为一个与原问题相似规模较小的问题来进行求解&#xff1b; 递归每次调用传入的是不同的变量 递归不是算法&#xff0c;是调用自己的过程 调用的那个是一个小问题&#xff0c;自己是一个…

鼠标右键单击Git Bash here不可用

最近在学习git时突然发现右键的git bash没反应&#xff0c;但是去点击应用图标就能正常运行&#xff0c;通常是因为你在安装git之后改变了它的目录名称或者位置&#xff0c;我就是因为安装后改变了一个文件夹的文件名导致不可用 在安装git时系统会默认给鼠标右键选项的git Bas…

【0x0002】HCI_Inquiry_Cancel命令详解

目录 一、命令概述 二、命令格式及参数说明 三、返回事件及参数说明 3.1. HCI_Command_Complete事件 3.2. Status 3.3. 示例 四、命令执行过程 4.1. 前提条件检查 4.2. 命令构建与发送 4.3. 控制器处理 4.4. 返回状态参数 4.5. 主机接收反馈与处理 4.6. 执行流程结…

OpenAI 12Days 第二天 强化微调(RFT):推动语言模型在科学研究中的应用

OpenAI 12Days 第二天 强化微调&#xff08;RFT&#xff09;&#xff1a;推动语言模型在科学研究中的应用 文章目录 OpenAI 12Days 第二天 强化微调&#xff08;RFT&#xff09;&#xff1a;推动语言模型在科学研究中的应用RFT的工作原理与应用领域案例研究&#xff1a;基因突变…

公共云提供商正在错失人工智能机遇

他们目前的成功和增长得益于人工智能的应用&#xff0c;但从长远来看&#xff0c;不可持续的成本和可行的替代方案可能会让企业望而却步。 生成式人工智能正在蓬勃发展&#xff0c;并且将继续蓬勃发展。因此&#xff0c;本地和公共云提供商都看到了对其人工智能产品的需求激增…

【Linux系列】AWK 使用指南

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

什么是 k8s CNI ?

1、什么是 CNI &#xff1f; CNI 是容器网络接口 &#xff08;Container Network Interface&#xff09;的缩写。定义了容器运行时如何与网络插件进行交互&#xff0c;从而管理容器网络。只要开发者遵循 CNI 定义的规范就可以接入 kubernetes &#xff0c;为 Pod 创建虚拟网卡…

深入理解进程的退出、等待与替换(Linux系统)

个人主页&#xff1a;敲上瘾-CSDN博客 个人专栏&#xff1a;Linux学习、游戏、数据结构、c语言基础、c学习、算法 目录 一、进程退出 1.退出场景 2.常见退出方法 3.退出码与退出信号 4._exit函数与exit函数 二、进程等待 1.什么是进程等待&#xff08;是什么&#xff1f;…

【初阶数据结构与算法】二叉树链式结构刷题训练(Leetcode二叉树遍历、单值二叉树、相同的树、另一棵树的子树、对称二叉树)

文章目录 一、二叉树的遍历二、单值二叉树三、相同的树四、另一颗树的子树五、对称二叉树 一、二叉树的遍历 在链式二叉树的定义与实现中我们已经详细讲解了二叉树常见的三种遍历方式&#xff0c;以及层序遍历&#xff0c;这里给出链接&#xff1a;【初阶数据结构与算法】二叉树…

深入浅出 Go 语言 sync包中的互斥锁、条件变量

深入浅出 Go 语言 sync包中的互斥锁、条件变量 引言 在并发编程中&#xff0c;多个 Goroutine 同时访问共享资源可能会导致数据竞争&#xff08;Race Condition&#xff09;&#xff0c;进而引发程序的不一致性或崩溃。为了确保并发程序的正确性和稳定性&#xff0c;Go 语言提…

制造业数据集成案例分享:3小时内实现MySQL到MySQL数据对接

ZZ刷新生产用料清单四化库存-制造一处-3小时&#xff1a;MySQL到MySQL数据集成案例分享 在现代制造业中&#xff0c;实时、准确的数据流动是确保生产效率和资源优化的关键。本文将分享一个实际运行的系统对接集成案例——“ZZ刷新生产用料清单四化库存-制造一处-3小时”&#…

OpenCV 图像基本操作

OpenCV快速通关 第一章&#xff1a;OpenCV 图像基本操作 第二章&#xff1a;OpenCV 图像基本操作 OpenCV 图像基本操作 OpenCV快速通关第二章&#xff1a;OpenCV 图像基本操作一、相关结构体与函数介绍&#xff08;一&#xff09;cv::Mat 结构体&#xff08;二&#xff09;cv:…

雨晨 2610(2)0.2510 Windows 11 24H2 Iot 企业版 LTSC 2024 极简 2in1

文件: 雨晨 2610(2)0.2510 Windows 11 24H2 Iot 企业版 LTSC 2024 极简 2in1 install.esd 索引: 1 名称: Windows 11 IoT 企业版 LTSC 极简 26100.2510 描述: Windows 11 IoT 企业版 LTSC 极简 26100.2510 By YCDISM RTM 2025 24-12-07 大小: 8,176,452,990 个字节 索引: 2 …

PHP保存base64编码图片,图片有一部分是灰色块儿,原因和解决办法

文章目录 场景原因解决方案完整的代码前端代码php代码 场景 我有个需求&#xff0c;移动端h5上传多张的图片。用input file可以上传多张&#xff0c;但是现在照片体积越来越大&#xff0c;同时上传多张会因为体积过大&#xff0c;导致上传失败。如果是小程序会好很多&#xff…