TOP_K问题以及堆的应用

TOP_K问题

问题简介

找出一个列表中前k大的数

三种解决方案

  • 排序后切片,时间复杂度O(nlogn+k)

  • 简单排序:冒泡排序为例,时间复杂度O(kn)

  • 小根堆,时间复杂度【建堆klogk + (n-k)logk = nlogk】

时间复杂度比较

# -*- encoding: utf-8 -*-
"""
@File    : top_k.py
@Time    : 2020/1/15 12:38 下午
@Author  : zhengjiani
@Email   : 936089353@qq.com
@Software: PyCharm
"""
# 找前5个最大的,使用小根堆
# 使用内置heapq
import heapq

from leetcode.cal_time import cal_time

"""
1.取列表前k个元素建立一个小根堆,堆顶就是目前第K大的数
2.依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整
3.遍历列表所有元素后,倒序弹出堆顶
"""
@cal_time
def func_1():
    li = [9,5,7,8,2,6,4,1,3]
    return heapq.nlargest(5,li)

@cal_time
def func_2():
    # 排序后切片
    li = [9, 5, 7, 8, 2, 6, 4, 1, 3]
    li.sort()
    return li[:3:-1]


@cal_time
def func_3():
    li = [9, 5, 7, 8, 2, 6, 4, 1, 3]
    _bubble_sort(li, 5)
    return li[:3:-1]


# 冒泡排序,只冒前5位
def _bubble_sort(li, k):
    for i in range(k):
        exchange = False
        for j in range(len(li) - i - 1):
            if li[j] > li[j + 1]:
                li[j], li[j + 1] = li[j + 1], li[j]
                exchange = True
        if not exchange:
            break

print(func_1())
print(func_2())
print(func_3())

三种方案的时间复杂度比较

== 计算时间的装饰器函数 ==

# -*- encoding: utf-8 -*-
"""
@File    : cal_time.py
@Time    : 2020/1/3 5:51 下午
@Author  : zhengjiani
@Email   : 936089353@qq.com
@Software: PyCharm
"""
import time
def cal_time(func):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        result = func(*args, **kwargs)
        t2 = time.time()
        print("%s running time: %s secs." % (func.__name__,t2-t1))
        return result
    return wrapper

构造堆以及堆排序

# -*- encoding: utf-8 -*-
"""
@File    : heap_sort.py
@Time    : 2020/1/14 2:22 下午
@Author  : zhengjiani
@Email   : 936089353@qq.com
@Software: PyCharm
"""
# 堆排序
# 大根堆
def sift(li,low,high):
    # li表示树,low表示树根,high表示树的最后一个节点的位置
    tmp = li[low]
    i = low
    j = 2 * i + 1 # 初始j指向空位的左孩子
    # i指向空位,j指向两个孩子
    while j <= high: # 循环退出的第二种情况:j>high,说明空位i是叶子节点
        if j+1 <= high and li[j] < li[j+1]: # 如果右孩子存在并且比左孩子大,指向右孩子
            j += 1
        if li[j] > tmp:
            li[i] = li[j]
            i = j
            j = 2 * i + 1
        else: # 循环退出的第一种情况:j位置的值都比tmp小,说明两个孩子都比tmp小
            break
    li[i] = tmp

# 小根堆
def sift_small(li,low,high):
    # li表示树,low表示树根,high表示树的最后一个节点的位置
    tmp = li[low]
    i = low
    j = 2 * i + 1 # 初始j指向空位的左孩子
    # i指向空位,j指向两个孩子
    while j <= high: # 循环退出的第二种情况:j>high,说明空位i是叶子节点
        if j+1 <= high and li[j] > li[j+1]: # 如果右孩子存在并且比左孩子大,指向右孩子
            j += 1
        if li[j] < tmp:
            li[i] = li[j]
            i = j
            j = 2 * i + 1
        else: # 循环退出的第一种情况:j位置的值都比tmp大,说明两个孩子都比tmp小
            break
    li[i] = tmp
    
def heap_sort_small(li):
    n = len(li)
    # 1.构造堆
    for low in range(n//2-1, -1, -1):
        sift_small(li, low, n-1)
    # 2.挨个出数
    for high in range(n-1, -1, -1):
        li[0], li[high] = li[high], li[0] # 退休棋子
        sift_small(li, 0, high-1)
        
# 构造堆,要求是完全二叉树,整个堆最后一个元素的位置当作high
def heap_sort(li):
    n = len(li)
    # 1.构造堆
    for low in range(n//2-1, -1, -1):
        sift(li, low, n-1)
    # 2.挨个出数
    for high in range(n-1, -1, -1):
        li[0], li[high] = li[high], li[0] # 退休棋子
        sift(li, 0, high-1)

堆的应用 —— 最小的K个数

leetcode 题

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
case1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

根据题解中的思路leetcode题解
3. 大根堆方法
大根堆——父节点的值大于或等于子节点的值

主要看思路二
思路二: 将给定数组前 K 个数据进行大根堆 heap 的排序,然后从第 i = k+1 个元素开始跟大根堆的第一个元素,也就是当前 K个元素里面的最大值进行比较: 当 arr[i] < heap[0] 时,当堆外元素< 当前大根堆堆顶元素时,我们将交换这两个元素,重新进行大根堆的排序,如此进行 n-k次,我们的堆中就是我们最后要的结果.
这个思路的时间复杂度为O(NlogK),空间复杂度为O(K).此方法在海量数据时应用比较好,因为我们的内存有限不能将全部数据读入.

class Solution2:
    def getLeastNumbers(self, arr, k):
        # 使用大根堆的方法
        if k <= 0 or k > len(arr):
            return []
        heap = self.build_heap(arr[:k])
        for i in range(k,len(arr)):
            if arr[i] < heap[0]:
                # 交换
                heap[0] = arr[i]
                self.sink(heap,0)
        return heap

    # 大根堆
    def sink(self,array,k):
        n = len(array)
        left = 2*k + 1
        right = 2*k + 2
        if left >= n: return
        max_i = left
        if right < n and array[left] < array[right]:
            max_i = right
        # 根据前k个元素进行建堆
        if array[max_i] > array[k]:
            array[max_i],array[k] = array[k],array[max_i]
            self.sink(array,max_i)

    #建堆(堆化数组)
    def build_heap(self,list_):
        n = len(list_)
        for i in range(n//2,-1,-1):
            self.sink(list_,1)
        return list_
 if __name__ == "__main__":
    arr = [3,2,1]
    k = 2
    s2 = Solution2()
    print(s2.getLeastNumbers(arr, k)) #[2,1]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值