本节之前,需要了解完全二叉树的概念
堆排序:
堆是特殊的完全二叉树,分为大根堆与小根堆
大根堆:满足任意节点都比其孩子节点大
小根堆:满足任意节点都比其孩子节点小
堆的排序过程如下:
1.建立堆;
2.得到堆顶元素为最大元素;
3.去掉堆顶,将最后一个元素放到堆顶,此时通过一次调整,重新使堆有序;
4.堆顶元素为第二大元素;
5.重复步骤3,直到堆排空。
在开始之前,我们要先明白向下调整是怎样完成的,即某一节点与其左右子节点较大的数作比,若子节点较大,则交换,否则不变。
时间复杂度为O(nlogn)
建立堆时,从最后一个非叶子节点向根节点进行向下调整
def sift(li, low, high): # 向下调整函数
"""
:param li: 传入列表
:param low: 树的根节点
:param high: 树的最后一个节点位置
:return:
"""
empty = li[low] # 将根节点空出
i = low # i用来记录空出的位置
j = 2 * i + 1 # i的左孩子记作j
while j <= high :
if j + 1 <= high and li[j + 1] > li[j] : # 如果右孩子存在并且比左孩子大
j = j + 1 # 指向右孩子
if empty < li[j] : # 某孩子比empty大
li[i] = li[j] # 将该位置的数放入空位置
i = j # 将空位置更新
j = 2 * i + 1 # j指向下一层,寻找empty元素应在的位置
else : # 左右孩子都不如empty大
break # 当前i的位置就是empty应在的位置
li[i] = empty
def heap_sort(li) :
# 建堆
l = len(li)
for i in range((l - 2) // 2, -1, -1):
# i表示向下调整时,子树根节点坐标
sift(li, i, l - 1)
# 建堆完成
# for i in range(0, l - 1):
# li[0], li[l - 1 - i] = li[l - 1 - i], li[0]
# sift(li, 0, l - 2 -i)
for i in range(l - 1, -1, -1): # 用i记录树的最后一个叶子节点
li[0], li[i] = li[i], li[0] # 交换过后,li[i]不再是堆内元素
print(i)
print(li)
sift(li, 0, i-1)
print(li)
return li
s = [2, 5, 1, 6, 7, 3, 9, 8, 4]
print(heap_sort(s))
8
[4, 8, 3, 6, 7, 2, 1, 5, 9]
[8, 7, 3, 6, 4, 2, 1, 5, 9]
7
[5, 7, 3, 6, 4, 2, 1, 8, 9]
[7, 6, 3, 5, 4, 2, 1, 8, 9]
6
[1, 6, 3, 5, 4, 2, 7, 8, 9]
[6, 5, 3, 1, 4, 2, 7, 8, 9]
5
[2, 5, 3, 1, 4, 6, 7, 8, 9]
[5, 4, 3, 1, 2, 6, 7, 8, 9]
4
[2, 4, 3, 1, 5, 6, 7, 8, 9]
[4, 2, 3, 1, 5, 6, 7, 8, 9]
3
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[3, 2, 1, 4, 5, 6, 7, 8, 9]
2
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 1, 3, 4, 5, 6, 7, 8, 9]
1
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
0
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
应用:topk问题
给定m个数,找到其中最小的k个数
1、建立一个元素个数为k的大根堆,因为大根堆中,根节点元素一定最大
2、将剩余的数与根节点比较,若小于根节点则替换,并向下调整一次,保证根节点最大
3、将大根堆排序
代码的时间复杂度为O(mlogk) : 遍历(m - k)个数,其中每次复杂度是logk
def topk(li, k):
tli = li[0: k] # 先取出前k个数
for i in range((k - 2) // 2, -1, -1) :
sift(tli, i, k-1) # 将tli从最后一个非叶子节点向根节点逐步向下调整为大根堆
for i in range(k, len(li) - 1) : # 将li中剩余的元素依次与当前根节点作比
if li[i] < tli[0] :
tli[0] = li[i]
sift(tli, 0, k-1) # 保证当前根节点是最大元素
for i in range(k - 1, -1, -1) :
tli[0], tli[i] = tli[i], tli[0]
sift(tli, 0, i-1)
return tli
import random
li = list(range(50))
random.shuffle(li) # 打乱li
print(li)
print(topk(li, 10))
[42, 18, 48, 45, 23, 2, 35, 25, 24, 43, 8, 36, 16, 39, 12, 13, 32, 30, 20, 31, 14, 34, 41, 0, 15, 44, 29, 33, 22, 1, 6, 49, 7, 46, 37, 5, 10, 28, 11, 17, 47, 9, 27, 26, 3, 4, 38, 40, 19, 21]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]