python堆排序的库_Python堆排序,以及堆的应用(LeetCode-295、215)

python堆排序

堆排思路

建立大根堆:将数组中所有数入堆,即对所有数进行一遍 heap_insert() 操作;

将该大根堆堆顶元素与堆中最后一个元素交换,然后 heap_size - 1 ;

对当前堆顶变化的元素,进行一次 heapify() 操作,使heap重新变为大根堆;

重复2~3步骤,直至 heap_size = 1 .

堆排序代码

实际上主要是写清楚heap_insert()和heapify()的操作

class Solution():

def heap_sort(self, nums):

heap_size = len(nums) - 1

for i in range(heap_size):

self.heap_insert(nums, i)

nums[-1], nums[0] = nums[0], nums[-1]

heap_size -= 1

while heap_size >= 1:

self.heapify(nums, 0, heap_size)

nums[heap_size], nums[0] = nums[0], nums[heap_size]

heap_size -= 1

return nums

def heap_insert(self, heap, index):

while heap[index] > heap[int((index - 1) / 2)]: # 当前入堆的数(heap[index])比父节点的大,那么二者交换

heap[index], heap[int((index - 1) / 2)] = heap[int((index - 1) / 2)], heap[index]

index = int((index - 1) / 2) # 继续判断交换上去的父节点

return heap

def heapify(self, heap, index, heap_size):

left = index * 2 + 1

while left <= heap_size:

largest = left + 1 if left + 1 <= heap_size and heap[left + 1] > heap[left] else left

largest = largest if heap[largest] > heap[index] else index

if largest == index: # 左右子节点都没父节点index大,heapify已经做完,跳出

break

else:

heap[largest], heap[index] = heap[index], heap[largest]

index = largest

left = index * 2 + 1

return heap

使用Python现成库 heapq

使用heapq的自带方法,利用小根堆实现一遍堆排

import heapq

class Laborer():

def heap_sort(self, arr):

small_heap = []

[heapq.heappush(small_heap, i) for i in arr] # 建堆 --> heap_insert == heappush

res = []

while len(small_heap) > 0:

res.append(small_heap[0]) # 小根堆,堆顶就是最小值

small_heap = small_heap[1:] # 每次将最小值排除出堆

heapq.heapify(small_heap) # 然后剩余的进行heapify再建小根堆

return res

heapq默认建的是小根堆,如何建大根堆?

这个思路其实很巧妙:

由于小根堆定义是:在任意子结构中,父节点的数值都要比左右2个子节点大;而大根堆定义刚好相反——父节点的数值都要比左右2个子节点小。所以说,在 heappush()的数值比较过程中,我们只需将if a > b ? 这样的不等式判断方向转变为 if a < b ?。对于小根堆而言,假设a是当前父节点、b是数值较大的子节点,那么第一个a > b成立的情况下,要小根堆要进行父子节点数值的替换;对于大根堆,则是在a < b成立的情况下,要大根堆要进行父子节点数值的替换。

看到了吗?这除了这一点判断不等式符号不同外,其余操作是一样的。如何让不等式符号变号?这是小学数学问题——两边取反。所以这也是利用小根堆建大根堆的秘诀:

压入小根堆时,对数值取反再压入

从该小根堆取出时,再将取出的数取反,还原回来

这样一来,你就可以把这个实际上仍是小根堆的堆当大根堆使用了。

使用heapq的自带方法,利用大根堆实现一遍堆排

class Laborer2:

def heap_sort(self, arr):

big_heap = []

[heapq.heappush(big_heap, -i) for i in arr] # 大根堆,要压入相反数

res = []

while len(big_heap) > 0:

res.append(-big_heap[0]) # 由于压入时是相反数,所以取出时一定要再取反还原回来;这时取出的是根节点最大值

big_heap = big_heap[1:] # 第一个数(父节点)出堆

heapq.heapify(big_heap) # 剩下元素heapify成大根堆

return res[::-1] # res是按照从大到小的顺序排的,所以要反过来返回

堆排的应用题

这题可以用partition思想解决,实际上这种题也很适合用堆解决。仔细想来,不论是快排还是堆排,都是都是每次定一部分/一个数字,所以对于第K大的问题,显然不需要全员排序,那么堆排和快排的思想肯定就可以起到加速作用,起码比直接sorted更好

对于本题,实际上想到堆,思路就很多了。就像假设我们用小根堆(如下code),小根堆永远只能拿到最小的那个数,那么我们pop掉最小的数 len-K次,自然就可以得到第 len-K 小的数,也就是第K大的数字。

class Laborer3:

def findKthLargest(self, nums, k: int):

# 第K大,即第len-K+1小。

# 小根堆,size=len,若pop掉(len-K-1)个堆顶,则第len-K+1小(即第K大)的就是当前小根堆堆顶

small_heap = []

[heapq.heappush(small_heap, i) for i in nums]

pop_num = 0

while pop_num < len(nums) - k:

small_heap.pop(0) # pop掉当前堆顶,最小值

heapq.heapify(small_heap) # 剩下元素建小根堆

pop_num += 1

return small_heap[0]

这是一道很有趣的题目。数据流中的中位数,用堆来解决的话,要建立两个堆,一个大根堆、一个小根堆。小根堆存储当前数据流流出的较大的N/2的数,大根堆存储流出的较小的N/2的数。如此一来,如果一直保持两个堆的平衡(两个堆最大容量差距不超过1)的情况下,任何时候求中位数,只需查看小根堆堆顶和大根堆堆顶即可,因为小根堆堆顶是前N/2大的数里最小的,大根堆堆顶则是后N/2大的数里最大的,两者刚好落在中间,中位数立即可以求得。

import heapq

class MedianFinder:

def __init__(self):

"""

initialize your data structure here.

"""

self.small_heap = [] # 小根堆放大数

self.big_heap = [] # 大根堆放小数

def adjust(self):

# 每次完成新数入堆后,做一次adjust操作,令两堆平衡

if len(self.small_heap) - len(self.big_heap) > 1:

# 小根堆抛出堆顶至大根堆

element = self.small_heap.pop(0)

heapq.heapify(self.small_heap)

heapq.heappush(self.big_heap, -element) # 注意,大根堆压入的数据是相反数!

elif len(self.big_heap) - len(self.small_heap) > 1:

# 大根堆堆顶抛出至小根堆

element = -self.big_heap.pop(0) # 大根堆抛出时记得还原

heapq.heapify(self.big_heap)

heapq.heappush(self.small_heap, element)

def addNum(self, num: int) -> None:

if len(self.small_heap) == 0:

self.small_heap.append(num)

else:

if num >= self.small_heap[0]:

# 当前数大于小根堆堆顶,就一定进小根堆。剩下的,先进大根堆再说

heapq.heappush(self.small_heap, num)

else:

heapq.heappush(self.big_heap, -num) # 同样的,大根堆是相反数插入!

self.adjust()

def findMedian(self) -> float:

if len(self.small_heap) != len(self.big_heap):

return self.small_heap[0] if len(self.small_heap) > len(self.big_heap) else -self.big_heap[0]

else:

return (self.small_heap[0] - self.big_heap[0]) / 2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值