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