python-排序算法(四)、堆排序之topk问题

本文探讨了如何使用Python的堆排序和冒泡排序算法解决Top K问题,比较了两者的时间复杂度,并通过实际代码展示了堆排序在大量数据中找出前k大数的效率。堆排序在100000个元素中找到前100个最大值的速度远超冒泡排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python-排序算法(三)、堆排序_simpleyako的博客-优快云博客

---------海量数据中找出前k大数(topk问题

应用场景
        通常在后端开发中,如果想要在若干个话题中,取出前100个最热的话题出来,这时我们就会涉及到topk问题,通常对于这种问题,我第一时间想到的可能就是对若干个话题进行排序之后切片出来。

下面通过堆排序实现方法冒泡排序方法进行比较:

计算执行时间的装饰器:

def cal_time(func):
    def inner(*args, **kwargs):
        strat = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print('%s执行时间:%s' % (func.__name__, end - strat))
        return result

    return inner

1、堆排序实现

1)解决思路

  • 取列表k个元素建立一个小根堆后堆顶就是目前第K大的数。
  • 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素 如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次向下调整
  • 最后遍历列表所有元素后,倒序弹出堆顶

2、代码实现

# 堆的向下调整函数
def shit(li, low, height):
    i = low  # i指向堆顶的位置
    j = i * 2 + 1  # j目前指向i的右孩子的位置
    tmp = li[low]  # tmp等于当前堆顶元素
    while j <= height:
        if j + 1 <= height and li[j + 1] < li[j]:
            j = j + 1
        if li[j] < tmp:
            li[i] = li[j]
            i = j  # i 指向第二层(左边或右边)的子树的堆顶
            j = i * 2 + 1
        else:
            break  # 如果右孩子或者左孩子不大于tmp则循环退出
    li[i] = tmp

# 实现topk
@cal_time
def heap_sort_topk(li, k):
    heap = li[0:k]
    # 1、建立小根堆
    for i in range((k - 2) // 2, -1, -1):
        shit(heap, i, k - 1)
    # 2、拿堆顶与后面的元素对比
    for i in range(k, len(li) - 1):
        if li[i] > heap[0]:
            heap[0] = li[i]
            shit(heap, 0, k - 1)
    # 3.挨个出数,使得列表倒序
    for i in range(k - 1, -1, -1):
        heap[0], heap[i] = heap[i], heap[0]
        shit(heap, 0, i - 1)
    return heap



时间复杂度:

        O(klogn)

2、冒泡排序实现 

1)解决思路

  • 有序区在列表头,无序区在列表尾
  • 遍历k(列表长度)趟列表
  • 每走一趟从从无序区倒序依次取一个元素,与之前一个元素相比较,谁大谁往前走
  • 每走一趟有序区增加一个元素无序区减少一个元素

2)代码实现

@cal_time
def bubble_sort_topK(li, k):
    for i in range(k):
        for j in range(len(li) - 1, i, -1):
            if li[j] > li[j - 1]:
                li[j], li[j - 1] = li[j - 1], li[j]
    return li[0:k]

 时间复杂度:

               O(kn)

3、对比执行时间

li = list(range(0, 100000))
random.shuffle(li)
li1 = copy.deepcopy(li)
li2 = copy.deepcopy(li)
heap_sort_topk(li1, 100)
bubble_sort_topK(li2, 100)

# 结果
heap_sort_topk执行时间:0.005194902420043945
bubble_sort_topK执行时间:0.9621851444244385

### 使用不同排序算法解决TOP K问题 #### 排序法 对于数据集较小且存在排序需求的情况下,可以直接应用排序方法来获取前K个元素。此方法涉及先对整个集合执行一次完整的排序操作,之后选取排序后的前K个元素作为结果。这种方法虽然直观易懂,但在处理大规模数据时效率低下,因为其时间复杂度达到O(n log n)[^3]。 ```python def top_k_with_sorting(nums, k): sorted_nums = sorted(nums, reverse=True) # 对列表进行降序排列 return sorted_nums[:k] # 返回前k个最大的数 ``` #### 堆排序法 当面对的数据规模庞大而所需选出的K远小于总数量N时,采用堆排序是一种更为高效的选择。通过构建一个小根堆(用于寻找最大值)或大根堆(用于寻找最小值),可以在遍历过程中动态保持住当前遇到的最大/小的K项记录。该策略能够显著减少不必要的比较次数,并将整体性能控制在较为合理的范围内,即平均情况下为O(n log k),其中n代表输入序列长度[k][^4]。 具体来说,在Python中可以通过`heapq`模块轻松实现这一逻辑: ```python import heapq def top_k_with_heap(nums, k): min_heap = nums[:k] heapq.heapify(min_heap) # 初始化大小为k的小顶堆 for num in nums[k:]: if num > min_heap[0]: heapq.heappop(min_heap) heapq.heappush(min_heap, num) return [item for item in reversed([heapq.heappop(min_heap) for _ in range(len(min_heap))])] ``` #### 快速选择法 (QuickSelect) 作为一种基于划分技术的概率型算法,快速选择能够在期望时间内以线性复杂度完成任务——即O(n)。尽管最坏情形下会退化至平方级别(O(n²)),但对于大多数实际应用场景而言已经足够优秀。它的工作原理类似于快速排序中的分区过程,只不过只关注一侧子区间直到定位到目标位置为止。 以下是使用Python实现的一个版本: ```python from random import randint def partition(arr, low, high): pivot_index = randint(low, high) arr[pivot_index], arr[high] = arr[high], arr[pivot_index] i = low - 1 for j in range(low, high): if arr[j] >= arr[high]: # 寻找较大的元素 i += 1 arr[i], arr[j] = arr[j], arr[i] arr[i + 1], arr[high] = arr[high], arr[i + 1] return i + 1 def quick_select_top_k(arr, k, start=None, end=None): if not isinstance(start, int): # 默认参数设置 start = 0 end = len(arr) - 1 while True: pos = partition(arr, start, end) if pos < k-1: # 如果pivot左边少于k个,则继续查找右侧部分 start = pos + 1 elif pos > k-1: # 同理,如果多于则转向左侧 end = pos - 1 else: # 找到了第k大的元素所在的位置 break return arr[:pos+1] nums = [7, 10, 4, 3, 20, 15] print(quick_select_top_k(nums.copy(), 3)) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值