堆排序

  • 大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
  • 小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
    在这里插入图片描述
  1. 堆的向下调整性质
    假设:节点的左右子树都是堆,但自身不是堆
    在这里插入图片描述
    在这里插入图片描述
    当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变换成一个堆。
  2. 堆排序过程
    【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)

  1. 堆排序内置模块
  • 优先队列:一些元素的集合,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) # 若小根堆,弹出最小元素,再存入列表,实现堆排序
  1. 堆排序扩展——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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值