快速选择算法:如何在 10 亿数据中瞬间找到“第 K 大”?

在海量数据处理面试中,有一个经典问题: "如何从 10 亿个整数中找到最大的 100 个?"

如果你回答"先排序再取值",面试官可能会摇摇头。因为全量排序的复杂度是 O(N log N),而 快速选择 (Quick Select) 算法可以将平均复杂度降到 O(N) 。

今天我们就来拆解这个比快排还快的"找数神器"。

1. 为什么排序不是最优解?

假设我们要找第 K 大的元素:

  • 排序法 :把整个数组排好序,然后取下标 K。这就像为了找出班里最高的同学,让全班所有人按身高站成一排。
  • 堆排序法:维护一个大小为 K 的小顶堆。复杂度 O(N log K)。
  • 快速选择法 :利用快排的 partition 思想,每次只处理我们需要的那一半。我们不需要知道其他元素的精确顺序,只需要知道它们比目标大还是小。

2. 核心逻辑:分治与剪枝

快速选择的核心在于 partition(分区)操作:

  1. 随机选一个基准值 pivot。

  2. 把数组分成两部分:左边都比 pivot 小,右边都比 pivot 大。

  3. 此时 pivot 所在的位置就是它在全局排序后的最终位置。

  4. 关键一步:

    • 如果 pivot 的位置正好是 K,恭喜,找到了!
    • 如果 pivot 的位置比 K 小,说明目标在右半部分,递归右边。
    • 如果 pivot 的位置比 K 大,说明目标在左半部分,递归左边。

注意: 每次递归我们只处理一半的数据,所以速度极快。

3. 代码实现:JavaScript 版

ini 复制代码
function findKthLargest(nums, k) {
    // 转换为寻找下标为 nums.length - k 的元素
    const targetIndex = nums.length - k;
    return quickSelect(nums, 0, nums.length - 1, targetIndex);
}

function quickSelect(arr, left, right, targetIndex) {
    if (left === right) return arr[left];

    // 随机选择 pivot,避免最坏情况
    const randomIndex = left + Math.floor(Math.random() * (right - left + 1));
    [arr[left], arr[randomIndex]] = [arr[randomIndex], arr[left]];

    const pivotIndex = partition(arr, left, right);

    if (pivotIndex === targetIndex) {
        return arr[pivotIndex];
    } else if (pivotIndex < targetIndex) {
        // 目标在右边
        return quickSelect(arr, pivotIndex + 1, right, targetIndex);
    } else {
        // 目标在左边
        return quickSelect(arr, left, pivotIndex - 1, targetIndex);
    }
}

function partition(arr, left, right) {
    const pivot = arr[left];
    let i = left + 1;
    let j = right;

    while (true) {
        while (i <= j && arr[i] < pivot) i++;
        while (i <= j && arr[j] > pivot) j--;
        if (i > j) break;
        [arr[i], arr[j]] = [arr[j], arr[i]];
        i++;
        j--;
    }
    
    [arr[left], arr[j]] = [arr[j], arr[left]];
    return j;
}

4. 性能分析:为什么它是 O(N)?

  • 平均情况 :每次 partition 都能把数据大致对半劈开。 N+N/2+N/4+...=2NN+N/2+N/4+...=2N,即 O(N) 。

  • 最坏情况 :每次选的 pivot 都是最大或最小值,退化为 O(N²)。

    • 优化方案 :使用随机化 Pivot(如代码所示)或"三数取中法",可以极大概率避免最坏情况。

5. 工业界应用:数据库与监控系统

  1. 数据库查询优化:

    • 当执行 SELECT * FROM table ORDER BY score DESC LIMIT 10 时,数据库引擎内部往往不会全表排序,而是使用类似快速选择或堆的算法来加速。
  2. 实时监控 Top N:

    • 统计系统中占用 CPU 最高的前 10 个进程。
  3. 电商排行榜:

    • 实时计算销量最高的商品列表。

6. 总结与面试考点

特性 说明
核心优势 平均 O(N) 时间复杂度,无需额外空间
稳定性 不稳定排序(会打乱原始顺序)
适用场景 寻找第 K 大/小元素、Top K 问题

面试官常问:

  1. "快速选择和快速排序的区别?" (答:快排递归处理左右两边,快选只递归一边。)
  2. "如果数据量太大内存放不下怎么办?" (答:使用外部排序或多路归并,或者维护一个大小为 K 的堆。)

下期预告: 找数搞定了,接下来我们聊聊处理字符串和数组的万能钥匙------滑动窗口 (Sliding Window) 。看看它是如何优雅解决"最长无重复子串"问题的。

如果你觉得这篇干货对你有帮助,欢迎点赞收藏!🚀


相关推荐
ZhengEnCi9 小时前
GPT-6 Astra 爆改 Qwen 权重真有那么神吗:权重空间编程 0.016% 参数的真相
算法·架构
罗西的思考10 小时前
【轻量化复刻大模型 / KIMI K3】KDA(Kimi Delta Attention):并行
人工智能·算法·机器学习
幸运小猿10 小时前
模拟慢上传-处理超时重试image 丢失线上问题
前端·javascript·html
夜不会漫长12 小时前
C++:红黑树
数据结构·算法
AI备案指南-满满13 小时前
算法备案审核不通过,一般是什么原因?
java·人工智能·算法·大模型备案·算法备案·大模型登记
速易达网络13 小时前
ECharts 图表制作完全指南:从零到可视化实战
前端·javascript·echarts
Ramble_Naylor13 小时前
Rust深度解读:Send 与 Sync 的本质与线程安全边界
python·算法·rust
秋天的怀念_David13 小时前
算法一千零一夜 · 第二十三夜 电力民生写成的曲线
java·程序人生·算法
不一样的故事12613 小时前
NB/T 47013.8-2015 附录D 吸枪技术
人工智能·经验分享·算法
通问AI13 小时前
用 200 行 Python 给文档库做「上下文完整度」自检:四个可复算的检查项
算法