时间复杂度O(nlogn)
运行时间对比:快排<归并<堆排
极端情况下:
快排:极端下效率低
归并:需要额外的内存开销
堆排:在极端快的排序中相对较慢
堆是特殊的完全二叉树,有大顶堆(结点的值大于左右孩子)和小顶堆(结点的值小于左右孩子)
如有列表[7,6,3,4,1,2,5,9,0,8]
则二叉树为

                7
          6           3
    4        1      2    5
 9    0   8

先调整,使他从大到小有序排列,也就是最高结点下的两个堆里都按大小顺序排

                9
          8            5
     6        7     2    3
 4    0    1              

这时候最大的数字就在最上面,然后开始把数字依次弹出排序,这里为了节省内存就将其放置在最后,将其与最后的数调换位置,也就是9和1换个位置,然后下次排序时就不算最后的数,因为9是最大且放置在最后了,依次重复操作
代码如下

def sift(list, low, hight):
    i = low #i就是指向父结点
    j = 2 * i + 1 #j是指向左孩子,左孩子与父亲的关系就是2i+1
    tmp = list[i] #这个是父结点的值
    while j <= hight: #hight是最后的数,也就是孩子还在这个堆里时
        if j < hight and list[j] < list[j+1]: #如果存在右孩子且右孩子比左孩子大则j指向右孩子
            j += 1
        if tmp < list[j]: #对比j指向的值是否大于父结点,如果大则进行以下操作
            list[i] = list[j] #j指向的孩子代替父结点的值
            i = j #i原来是指向父结点的,现在指向j结点也就是孩子结点
            j = 2 * i + 1 #这些调整结束,然后j指向孩子结点的左孩子,便于while的循环操作
        else:
            break 
    list[i] = tmp #这时候的i指向可能是孩子结点,所以将原来父结点的值放置于孩子结点里

def heap(list):
    n = len(list)
    for i in range(n // 2 - 1, -1, -1): #对二叉树进行排序,且n//2-1为最后一个数的父结点的位置,这个时候是从最后的父结点开始排
        sift(list, i, n-1) #排完序就是9在最上面,左右孩子的堆都排完序,这时候列表就是[9,8,5,6,7,2,3,4,0,1]
    for i in range(n -1,-1,-1): #对于已排完序的二叉树将最上面的数依次弹出,放置于最后
        list[0],list[i] = list[i],list[0] #最上面的数与最后的数交换,第一次就是9和1进行交换,
下次交换最后的数不是9,因为下次交换不把9列入二叉树里
        sift(list,0,i-1) #由于前面已经交换,所以交换完最上面的数可能不是二叉树内最大的数(9已不算,这时候只算9个数字),
应该重新选择最大的数放置上面,就这样依次进行

ab = [7,6,3,4,1,2,5,9,0,8]
heap(ab)
print(ab)