一、前言
堆:一种特殊的完全二叉树结构。
大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大;
小根堆:一棵完全二叉树,满足任一节点都比其他孩子节点小。
二、堆排序
堆排序过程:
- 建立大根堆
- 将堆顶元素与最后一个元素进行交换
- 将交换后的堆,重新调整为大根堆
- 重复2,3步骤,直到全部顺序排序
其中,建立大根堆和重新调整为大根堆的主体方法是相同的。其实现方法如下:
def adjust(lis,low,high):
'''
:param lis:除了根节点,其左右子树都要满足大根堆性质
:param low: 最开始根节点的位置
:param high:最后一个元素位置
:return:
'''
i=low
tmp=lis[i]
j=2*i+1
while j+1<=high:
if lis[j]<lis[j+1]:
j=j+1
if lis[i]<lis[j]:
lis[i],lis[j]=lis[j],lis[i]
i = j
j = 2 * i + 1
else:
break
if j==high:
if lis[j]>lis[i]:
lis[j],lis[i]=lis[i],lis[j]
i=j
lis[i]=tmp
return lis
建立大根堆的过程,其实就是从最后一个非叶子节点到根节点依次调用上述方法,具体实现方法如下:
# 建堆
def build_stack(lis):
#从下往上调整每个子二叉树,则最后就能得到一个大顶堆(或小顶堆)
n=len(lis)
for i in range ((n-2)//2,-1,-1):#最后一个非叶子节点的索引为(n-2)//2
adjust(lis,i,n-1)
return lis
最后排序过程,就是依次交换,再调用调整方法
#排序
def sort(lis):
n=len(lis)
for i in range(n-1,0,-1):#依次,根顶与最后的元素交换
lis[i],lis[0]=lis[0],lis[i]
adjust(lis,0,i-1)
return lis
完成!
检查:
if __name__ == '__main__':
import random
li = [i for i in range(1000)]
random.shuffle(li)
lis = build_stack(li)
lis = sort(lis)
print(lis)
三、时间复杂度分析
1、初始化建堆
初始化建堆只需要对二叉树的非叶子节点调用adjust()函数,由下至上,由右至左选取非叶子节点来调用adjust()函数。那么倒数第二层的最右边的非叶子节点就是最后一个非叶子结点。
假设高度为k,则从倒数第二层右边的节点开始,这一层的节点都要执行子节点比较然后交换(如果顺序是对的就不用交换);倒数第三层呢,则会选择其子节点进行比较和交换,如果没交换就可以不用再执行下去了。如果交换了,那么又要选择一支子树进行比较和交换;高层也是这样逐渐递归。
那么总的时间计算为:s = 2^( i - 1 ) * ( k - i );其中 i 表示第几层,2^( i - 1) 表示该层上有多少个元素,( k - i) 表示子树上要下调比较的次数。
S = 2^(k-2) * 1 + 2^(k-3)2…..+2(k-2)+2^(0)*(k-1) ===> 因为叶子层不用交换,所以i从 k-1 开始到 1;
S = k2^k -k -1;又因为k为完全二叉树的深度,而log(n) =k,把此式带入;
得到:S = nlogn - log(n) -1,所以时间复杂度为:O(nlogn)
2、排序重建堆
在取出堆顶点放到对应位置并把原堆的最后一个节点填充到堆顶点之后,需要对堆进行重建,只需要对堆的顶点调用adjustheap()函数。
每次重建意味着有一个节点出堆,所以需要将堆的容量减一。adjustheap()函数的时间复杂度k=log(n),k为堆的层数。所以在每次重建时,随着堆的容量的减小,层数会下降,函数时间复杂度会变化。重建堆一共需要n-1次循环,每次循环的比较次数为log(i),则相加为:log2+log3+…+log(n-1)+log(n)≈log(n!)。可以证明log(n!)和nlog(n)是同阶函数:
∵(n/2)n/2≤n!≤nn,∵(n/2)n/2≤n!≤nn,
∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)
所以时间复杂度为O(nlogn)
3、总结
初始化建堆的时间复杂度为O(nlogn),排序重建堆的时间复杂度为nlog(n),所以总的时间复杂度为O(nlogn+nlogn)=O(nlogn)。
4、取topk最大值的时间复杂度分析
从n个取k个数,对k个数建小根堆,时间复杂度为klogk,然后依次从n-k个数中取元素,与小根堆的根进行比较,如果小于则跳过(因为该元素不可能为topk),如果大于则与其交换,然后从新调整堆,直到重新形成小根堆,重复这个过程,这个过程复杂度为(n-k)logk,最后小根堆的元素就是topk最大值。所以最后取topk最大值的时间复杂度为(n-k)logk+logk=nlogk,空间复杂度为O(n)