快速选择算法:如何在 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) 。看看它是如何优雅解决"最长无重复子串"问题的。

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


相关推荐
ℋᙚᵐⁱᒻᵉ鲸落2 小时前
移动端滑动手势冲突:overflow: auto导致外层横向滑动失效
前端·javascript·css·vue.js·html
毕竟是shy哥2 小时前
计算YOLO数据集中每个类的目标数
算法·yolo·机器学习
M78佐菲2 小时前
Linux学习笔记:TCP协议
linux·笔记·学习·tcp/ip·算法
默_笙3 小时前
🎃 前端学了 Next.js,后端该学啥?NestJS 就是 Node 版的蜜雪冰城
前端·javascript
晊晌_h3 小时前
嵌入式从0到精通——数据结构总结[特殊字符]
数据结构·算法·排序算法
我找到地球的支点啦4 小时前
Matlab系列(009) 一CRC循环冗余校验详解
开发语言·数据结构·算法·matlab·信息与通信
罗西的思考4 小时前
【OpenClaw具身硬件】ZeroClaw 源码阅读笔记(3)--- RAG
人工智能·算法·机器学习
এ慕ོ冬℘゜4 小时前
navigator 导航对象 BOM制作方法
javascript
浪里镖客5 小时前
位姿转换矩阵写法-个人习惯(计算机理解其实是相反的)
线性代数·算法·矩阵
kyriewen7 小时前
面试官让我用 AI 重构一个 8 年陈的 React 组件——他说他不看代码,只看我会不会拆
前端·javascript·面试