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

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


相关推荐
罗西的思考7 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX8 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
avi91119 小时前
[AI教做人]AI平台做2项目;一个3D模型展示,另一个框架多人
javascript·人工智能·ai·3d模型·3d引擎·顶点和法线
爱跳舞的烤冷面9 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
kyriewen9 小时前
我用Claude Code两天干完了团队两周的排期——周报发出去那一刻我就后悔了
前端·javascript·ai编程
猎嘤一号9 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽9 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI9 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
用户9385156350710 小时前
手写一个 LLM Harness 框架:用工程化手段把大模型幻觉踩在脚下
javascript·人工智能·后端
油丶酸萝卜别吃10 小时前
jquery-ajax.js 说明文档
前端·javascript·jquery