← 中文资源

简体中文版本

用 BinaryHeap 求前 K 大

用 Reverse 把最大堆变成最小堆,在流式数据中只保留前 K 个最大值,最后按从大到小输出。

阅读英文原文

功能说明

求前 K 大不必对整个数组排序。维护一个容量为 K 的堆,遍历每个元素时先入堆,超过容量就弹出堆顶,最终堆中剩下的就是最大的 K 个。这样时间复杂度为元素数乘以 log K,空间只与 K 有关,适合日志排行、分数榜和流式指标。

Rust 的 BinaryHeap 默认是最大堆,直接使用会弹出最大值,恰好与我们想保留的方向相反。用 std::cmp::Reverse 包装元素后,堆顶变成实际最小值,弹出时丢掉的正是当前候选中最小的一项,从而保留较大的值。

关键代码

top_k 先处理 K 为零的边界,避免容量为零时仍入堆。堆的容量预分配为 K 加一,允许先推入再判断是否超限。遍历使用按值解构,复制 i64。超过 K 时弹出堆顶,即最小的 Reverse。最后把堆迭代为普通值,此时顺序不是有序的,需要先排序再反转,才能得到从大到小的结果。使用不稳定排序即可,重复值的相对顺序不影响数值结果。

使用注意

K 大于元素数量时返回全部元素,仍按降序排列。K 为零返回空向量,调用方需自行处理。堆中保留的是元素值,若需要同时返回原始下标或记录,应把元组放入堆并实现相应比较,注意 Reverse 只反转第一比较键。浮点数不实现 Ord,不能直接入堆,应使用有序包装或整数化。数据量极大且 K 接近元素数时,整体排序可能更简单。该实现会复制值,大对象建议存引用或索引。

验证方式

运行脚本,输出 91、88、56,正是九个数字中最大的三个,且按降序排列。把 K 改为零应输出空向量,改为二十应输出全部九个数字。再输入包含重复值的数组,确认重复值被保留到相应数量。

代码示例

use std::cmp::Reverse;
use std::collections::BinaryHeap;

fn top_k(values: &[i64], k: usize) -> Vec<i64> {
    if k == 0 {
        return Vec::new();
    }
    let mut heap: BinaryHeap<Reverse<i64>> = BinaryHeap::with_capacity(k + 1);
    for &value in values {
        heap.push(Reverse(value));
        if heap.len() > k {
            heap.pop();
        }
    }
    let mut out: Vec<i64> = heap.into_iter().map(|Reverse(value)| value).collect();
    out.sort_unstable();
    out.reverse();
    out
}

fn main() {
    let values = [42, 7, 19, 3, 88, 23, 56, 91, 5];
    println!("{:?}", top_k(&values, 3));
}