- 堆
- 大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
- 小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
- 堆的向下调整性质
假设:节点的左右子树都是堆,但自身不是堆
当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变换成一个堆。 - 堆排序过程
【1】建立堆
【2】得到堆顶元素,为最大元素
【3】去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
【4】堆顶元素为第二大元素
【5】重复步骤3,直到堆变空
重点代码:
import random
# 向下调整函数
# 这个函数是将一个大根堆的根节点作为tmp存起来,
# 放到堆中合适的位置,最后交换根和棋子的位置
def sift(li, low, high): # low为根节点,high为最后一个叶子结点
tmp = li[low]
i = low
j = 2 * i + 1 # 左孩子索引
while j <= high: # 退出条件2:当前i位置是叶子结点,j位置超过了high
# j指向更大的孩子
if j + 1 <= high and li[j + 1] > li[j]: # j+1<=high保证该节点有右孩子
j = j + 1 # 如果右孩子存在并且更大,j指向右孩子
if tmp < li[j]: # 孩子节点的值更大
li[i] = li[j] # 让孩子节点往上走
i = j
j = 2 * i + 1
else: # 退出条件1:tmp的值大于两个孩子的值
break
li[i] = tmp # 根和棋子交换
def heap_sort(li):
# 1. 建堆
# i节点的父节点序号是(i-1)//2,如果序号是n-1节点,代入公式父节点(n//2-1)
n = len(li)
for i in range(n // 2 - 1, -1, -1):
# i是建堆时要调整的子树的根的下标
# 每个子树的high都设置为len(li)-1,不影响最后结果,
# 因为每个子树最后一个叶子结点的子节点(如果有的话)一定大于len(li)-1,
# 跳出sift中的while循环了
sift(li, i, n - 1)
# 2. 挨个出数
for i in range(n - 1, -1, -1): # i表示当前high值,也表示棋子的位置
li[i], li[0] = li[0], li[i]
# 现在堆的范围 0~i-1
sift(li, 0, i - 1)
if __name__ == '__main__':
li=list(range(10000))
random.shuffle(li)
heap_sort(li)
print(li)
*堆排序复杂度:o(nlogn)
- 堆排序内置模块
- 优先队列:一些元素的集合,POP操作每次执行都会从优先队列中弹出最大(或最小)的元素。
- 堆——优先队列
- Python内置模块——heapq
- heapify(x)
- heappush(heap, item)
- heappop(heap)
- 利用heapq模块实现堆排序
重点代码:
import heapq
li=list(range(10000))
random.shuffle(li)
print(li)
heapq.heapify(li) # 小根堆
print(li)
heapq.heappush(li,10) # 尾插
print(li)
heapq.heappop(li) # 若小根堆,弹出最小元素,再存入列表,实现堆排序
- 堆排序扩展——topK问题
现在有n个数,设计算法找出前k大的数(k<n)
解决思路:
- 取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数。
- 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整。
- 遍历列表所有元素后,倒序弹出堆顶。
def topk(li,k):
heap = li[0:k]
# 从最后一个非叶子节点开始调整堆
for i in range(k//2-1,-1,-1):
sift(heap,i,k-1)#i为根节点,k-1为最后一个叶子节点
# 现在小根堆里面存的暂时是前K大的元素,根节点最小
for i in range(k,len(li)):
if li[i]>heap[0]: # 后续列表中元素比heap[0](根节点)大,就替换,并向下调整
heap[0]=li[i]
sift(heap,0,k-1)
# 挨个出数
for i in range(k-1,-1,-1):
heap[0],heap[i]=heap[i],heap[0]
sift(heap,0,i-1)
推荐使用Python内置模块——heapq:
import heapq
li=list(range(10))
random.shuffle(li)
print(heapq.nsmallest(3,li))#堆中前三小的
print(heapq.nlargest(3,li))#堆中前三大的
这种方法时间复杂度o(nlogk)