前两天面试被问到堆,于是学习了一下堆排序
堆相关的二叉树基础
堆实际上是一个完全二叉树,所以先了解一下二叉树。二叉树(binary tree)是指树中节点的度不大于2的有序树。划重点,度不大于2,有序。
一些重要性质:
- 在二叉树的第层 i 最多有 2^(i-1) 个结点。
- 深度为k(k>=0)的二叉树最少有k个结点,最多有 2^k-1 个结点。
- 对于任一棵非空二叉树,若其叶结点数为n0,度为2的非叶结点数为n2,则n0=n2+1。
- 具有n个结点的完全二叉树的深度为比 log2(n+1) 大的第一个整数。
- 如果将一棵有n个结点的完全二叉树自顶向下,同一层自左向右连续给结点编号1,2,3,4…n,然后按此结点编号将树中各结点顺序的存放于一个一维数组,并简称编号为i的结点为结点i (i>=1&&i<=n), 则有以下关系:
(1)若 i = 1,则结点i为根,无父结点;若 i>1,则结点 i 的父结点为结点int_DOWN(i / 2);
(2)若 2 * i<=n,则结点i的左子女为结点2 * i (从父亲找左孩子) ; 结点i的右子女为结点2 * i+1(从父亲找右孩子)
(3)结点 i 所在的层次为 int_DOWN(log2(i)+1)。
完全二叉树:
满二叉树:每一层结点都达到了最大个数,除最底层结点的度为0外,其他各层结点的度都为2。
完全二叉树:上面从第1层到第k-1层的所有各层的结点数都是满的,仅最下面的第k层是满的,或从右往左连续缺少若干结点。也就是从树最底层从右到左有缺失的二叉树,记住得是一层一层的来。
堆
大根堆:所有父节点都比其子节点大。
小根堆:所有父节点都比其子节点小。
二叉树可以通过向下调整变成一个大根堆。从根节点开始,使得每一个小三角都变成一个大根堆,简单的理解就是大的带小的玩,如果不够资格就一直往下找有资格的。
B站讲解,特别清楚
构建堆
构建最小的堆然后一层一层扩充,直到整个树构成一个堆。
def sift(lis, root, last):
'''
完成一个节点的向下调整,也就是该节点比他的左孩子右孩子都大即可
root: 根节点的位置
last: 最后一个叶子节点的位置
'''
ii=root
ii_l=2*ii+1
ii_r=ii_l+1
while ii_l<=last:
if ii_r>last:
if lis[ii]<lis[ii_l]:
lis[ii],lis[ii_l]=lis[ii_l],lis[ii]
break
if lis[ii]>lis[ii_l] and lis[ii]>lis[ii_r]:
break
if lis[ii_l]>lis[ii_r]:
lis[ii],lis[ii_l]=lis[ii_l],lis[ii]
ii=ii_l
else:
lis[ii],lis[ii_r]=lis[ii_r],lis[ii]
ii=ii_r
ii_l=2*ii+1
ii_r=ii_l+1
return lis
上面我们完成的是一个当父亲时找到自己合理的位置的过程,对所有的节点做同样的事情就能构建堆,问题是如果从上往下有可能根节点没办法领导整棵树(比下面的某个非子节点还小),所以采用的思路是从下往上,把最小的二叉树调整成一个大根堆,然后再调整稍微大一点的树,一直到调整整棵树。
def heap_build(lis):
n=len(lis)
for ii in range((n-1)//2,-1,-1):
lis=sift(lis,ii,n-1)
return lis
堆排序
得先构建堆才能给堆排序。构建了堆以后,每次的根节点就是整个树最大的数字,放到新的列表里面去,然后把最后一个元素放到顶点上去重新向下调整,在拿走根节点,最后元素放上去,以此类推。
def heap_sort(lis):
lis=heap_build(lis)
res=[]
while len(lis)!=0:
res.append(lis[0])
lis[0]=lis[-1]
lis.pop()
lis=sift(lis,0,len(lis)-1)
return res
但是这个方法多占一个内存,简化一下
def heap_sort(lis):
lis=heap_build(lis)
n=len(lis)-1
for ii in range(n,-1,-1):
lis[0],lis[ii]=lis[ii],lis[0]
lis=sift(lis[:ii],0,ii-1)+lis[ii:]
return lis
python内置堆包heapq
常用函数
import heapq
lis=[3,4,2,6,7,1,9,8]
heapq.heapify(lis) # 构建堆
heapq.heappush(lis,20) # 给堆里面添加元素
lis1=lis[:]
lis_sort=[heapq.heappop(lis1) for ii in range(len(lis1))] # 堆排序输出
topK问题
从海量数字中寻找最大的 k 个.
- 思路1:先排序后切片取前k个复杂度是O(nlog(n))
- 思路2:冒泡排序,选择排序,插入排序,时间复杂度O(kn)
- 思路3:堆。先取前k个元素建立一个最小堆,然后遍历后面的元素维护这个最小堆,如果新元素比根节点小则忽略否则向下调整一次堆(O(logk))。整个时间复杂度是O(nlogk),比前两种思路都好。
实现代码:
import heapq
def topk(nums):
n=len(nums)
heap=nums[:k]
heapq.heapify(heap)
for ii in range(100,n):
if nums[ii]<heap[0]:
continue
else:
heap[0]=nums[ii]
heapq.heapify(heap)
return heap