一、引入
问题:目前共有n个数,设计算法得到前k大的数。(m<n)
解决思路:
- 排序后切片:O(n*logn+m) = O(n*logn)
- 排序LowB三人组:O(mn) 例如冒泡排序,交换m次,即可取前m大的数字;对于插入排序,维护一个长度为100的列表,将其排好序,再依次遍历其他数,将插入的数字放入其中,后面的数字往后挪;选择排序我们选择100次。
- 堆排序:O(nlogk)
二、堆排序解决思路
- 取列表前k个元素建立一个小根堆(从小到大),其堆顶就是目前第K大的元素。
- 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整。
- 最后能确保小根堆是我们需要的前K大的数。
- 遍历列表的所有元素后,倒序(因为是小根堆)弹出列表。
示例代码如下:
import random# 调整为小根堆
def sift(li, low, high): # 向下调整函数;初始条件应该为除了根节点,下面的子堆默认为有序的i = low # i最开始指向根节点j = 2 * i + 1 # 左孩子tmp = li[low] # 将堆顶元素存起来while j <= high: # 只要j上面有数,没跑到结构外if j + 1 <= high and li[j + 1] < li[j]: # 存在右孩子,且右孩子比左孩子大;不能交换j = j + 1 # j指向有孩子if li[j] < tmp:li[i] = li[j] # 换数i = j # 往下看一层j = 2 * i + 1else: # 此时tmp更大,将tmp放回原位li[i] = tmpbreak # 直接退出,因为sift默认下面就是堆的情况下else: # 两种退出循环的条件;当其越界li[i] = tmpdef topk(li, k):heap = li[0:k]# 1.取前K个元素构建小根堆for i in range((k - 2) // 2, -1, -1):sift(heap, i, k - 1)# 依次向后遍历原列表,如果小于堆顶,忽略;如果大于堆顶,进行交换,交换之后再进行一次调整for i in range(k, len(li)):if li[i] > 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)return heapli = [i for i in range(1000)]
random.shuffle(li)
print(li)
print(topk(li, 10))
输出结果如下: