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

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


相关推荐
用户962377954482 小时前
原理分析 | Controller —— SpringBoot 内存马
javascript·后端
|_⊙2 小时前
C++ 哈希
算法·哈希算法·散列表
写代码的皮筏艇2 小时前
replace方法
前端·javascript
睡觉就不困鸭2 小时前
第十九天 删除字符串中的所有相邻重复项 栈的经典应用。
算法
小O的算法实验室2 小时前
2026年AST,复杂边界环境下多无人机协同搜索攻击+分形智能自组织任务规划,深度解析+性能实测
算法·论文复现·智能算法·智能算法改进
测绘第一深情2 小时前
租用GPU云服务器进行深度学习(AutoDL,超保姆级,适用新手)
数据结构·人工智能·经验分享·python·深度学习·算法·计算机视觉
北顾笙9802 小时前
day34-数据结构力扣
数据结构·算法·leetcode
khalil10202 小时前
代码随想录算法训练营Day-36动态规划04 | 1049. 最后一块石头的重量 II、494. 目标和、474.一和零
算法·动态规划
码农爱学习2 小时前
用cJson的例子,来理解二级指针
算法