[特殊字符] TopK问题全解析(TomGo复习版|讲人话 + 原理打穿)

目录

一、TopK我到底在干嘛?

二、最直观但低级的方法(排序)

三、核心思想(这题灵魂🔥)

四、为什么用"小顶堆"?(必须搞透)

五、完整算法流程(必须背下来)

六、代码实现(核心)

[七、文件版本 TopK(工程思维🔥)](#七、文件版本 TopK(工程思维🔥))

八、文件操作(你这题用到的)

九、随机数(你这次踩坑的点🔥)

十、复杂度分析

十一、易错点(你必须记住🔥)

十二、面试官最爱问🔥


一、TopK我到底在干嘛?

一句话:

<<在一堆数据里,找出最大的 K 个数(或最小的 K 个)>>


⚠️ 但关键不在"找",而在:

👉 数据可能很多,不能(或不想)全部排序

比如:

  • 100万数据,只要前10个

  • 日志文件、数据流

👉 这时候排序就是浪费


二、最直观但低级的方法(排序)

qsort(arr, n, sizeof(int), cmp);

然后取前 K 个


❗ 问题

  • 时间复杂度:O(n log n)

  • 你只要 K 个,却排了 n 个

👉 明显过度计算

🎯 qsort

qsort 的时间复杂度来自快速排序,其核心是"分治思想"。每一轮通过 partition 将数组划分为两部分,单层时间复杂度为 O(n),在平均情况下递归深度为 log n,因此总体复杂度为 O(n log n)。在极端情况下(如数据有序),可能退化为 O(n²)。
🧠 再给你一个更深的理解(加分)

快排快的本质不是"比较少",而是:

每一层都在减少问题规模


三、核心思想(这题灵魂🔥)

<<❗ 用一个"大小为 K 的堆",动态维护结果>>


🧠 关键理解(非常重要)

TopK不是一次性找,而是:

<<不断淘汰不合格的,只留下最好的 K 个>>


四、为什么用"小顶堆"?(必须搞透)


🎯 我要找:最大的 K 个数

👉 用:小顶堆


🔍 为什么??(重点)

堆里只放 K 个数:

  • 堆顶 = 当前最小的

  • 它是"最弱的那个"


💡 本质一句话

<<用最小的那个,卡住门槛>>


🧪 举个例子

K = 3

当前堆: [5, 8, 10]

👉 5 是最小(堆顶)


新来一个数:

情况1:x = 4

👉 4 < 5 ❌

👉 连门槛都没过 → 直接扔


情况2:x = 12

👉 12 > 5 ✅

👉 替换掉 5


👉 然后重新调整堆

---

🎯 总结本质

<<堆顶永远是"最该被淘汰的人">>

---

五、完整算法流程(必须背下来)


Step 1️⃣:拿前 K 个建小堆

cpp 复制代码
for (int i = 0; i < k; i++)
{
    heap[i] = arr[i];
}

Step 2️⃣:建堆

cpp 复制代码
for (int i = (k - 2) / 2; i >= 0; i--)
{
    AdjustDown(heap, k, i);
}

❗ 为什么从 (k-2)/2 开始?

👉 因为这是最后一个非叶子节点


Step 3️⃣:遍历剩下的数据

cpp 复制代码
if (x > heap[0])
{
    heap[0] = x;
    AdjustDown(heap, k, 0);
}

❗ 为什么只用向下调整?

👉 因为:

<<只有堆顶被破坏,其余子树是正常的>>


六、代码实现(核心)


🔧 向下调整(小顶堆)

cpp 复制代码
void AdjustDown(int* a, int n, int parent)
{
    int child = parent * 2 + 1;

    while (child < n)
    {
        // 选更小的孩子
        if (child + 1 < n && a[child + 1] < a[child])
        {
            child++;
        }

        // 如果孩子比父亲小,就交换
        if (a[child] < a[parent])
        {
            int tmp = a[child];
            a[child] = a[parent];
            a[parent] = tmp;

            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

七、文件版本 TopK(工程思维🔥)


💡 为什么用文件?

<<❗ 因为数据可能大到放不进内存>>


完整代码

cpp 复制代码
#include "Heap.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 创建随机数据写入文件
void CreateNDate()
{
    int n = 1000;
    srand(time(0));

    FILE* fin = fopen("data.txt", "w");
    if (fin == NULL)
    {
        perror("fopen error");
        return;
    }

    for (int i = 0; i < n; ++i)
    {
        int x = (rand() + i) % 10000;
        fprintf(fin, "%d\n", x);
    }

    fclose(fin);
}

void text3()
{
    CreateNDate();

    int k = 10;
    int* heap = (int*)malloc(sizeof(int) * k);
    if (heap == NULL)
    {
        perror("malloc error");
        return;
    }

    FILE* fout = fopen("data.txt", "r");
    if (fout == NULL)
    {
        perror("fopen error");
        return;
    }

    // 读前k个
    for (int i = 0; i < k; i++)
    {
        fscanf(fout, "%d", &heap[i]);
    }

    // 建小堆
    for (int i = (k - 2) / 2; i >= 0; --i)
    {
        AdjustDown(heap, k, i);
    }

    int x = 0;
    while (fscanf(fout, "%d", &x) == 1)
    {
        if (x > heap[0])
        {
            heap[0] = x;
            AdjustDown(heap, k, 0);
        }
    }

    printf("TopK: ");
    for (int i = 0; i < k; i++)
    {
        printf("%d ", heap[i]);
    }
    printf("\n");

    fclose(fout);
    free(heap);
}

八、文件操作(你这题用到的)


fopen

FILE* f = fopen("data.txt", "r");

模式| 含义

r| 读

w| 写(会清空)


fscanf(重点)

fscanf(f, "%d", &x);

👉 返回值:

  • 1:成功

  • EOF:结束


❗ 正确写法

while (fscanf(f, "%d", &x) == 1)


fclose

fclose(fout);

👉 必须写,不然可能丢数据


九、随机数(你这次踩坑的点🔥)


rand 本质

<<❗ 伪随机数(不是完全随机)>>


srand 作用

srand(time(0));

👉 改变随机序列起点


❗ 重点结论

<<srand 不能避免重复,只是让每次结果不同>>


十、复杂度分析

方法| 复杂度

排序| O(n log n)

堆| O(n log k)


👉 当 k ≪ n 时:

<<堆是最优选择>>


十一、易错点(你必须记住🔥)


❌ 1. typedef 写反

typedef HPDateType int; ❌

👉 正确:

typedef int HPDateType; ✅


❌ 2. fscanf 写错

while (fscanf(...) > 0) ❌

👉 正确:

== 1


❌ 3. 忘记 fclose

👉 会导致数据没写入


❌ 4. 堆方向搞反

👉 TopK最大 → 小顶堆

👉 TopK最小 → 大顶堆


十二、面试官最爱问🔥


❓ 为什么用小顶堆?

👉 因为要淘汰最小的


❓ 为什么只维护 K 个?

👉 因为只关心前 K 个


❓ 为什么时间复杂度是 n log k?

👉 每次调整是 log k,总共 n 次


❓ 如果数据是流式的?

👉 仍然用小顶堆


十三、拓展(进阶)


⭐ 1. 数据流 TopK

👉 数据不断来:

  • 每来一个判断一次

  • 不需要全部存储


⭐ 2. 快速选择(QuickSelect)

👉 平均 O(n)

但:

  • 不稳定

  • 面试可能问


⭐ 3. TopK 频率问题

👉 用:

  • 哈希表 + 堆

🧠 最后一段TomGo总结(必须记住)

<<TopK 的本质不是"找最大",而是:

用一个固定大小的数据结构,持续淘汰不合格元素>>


🎯 我自己的理解(复盘用)

**- 堆顶 = 当前最弱的

  • 每次新数据都在"挑战门槛"
  • 能留下的,才是 TopK**
相关推荐
y = xⁿ1 小时前
【LeetCodehot100】T114:二叉树展开为链表 T105:从前序与中序遍历构造二叉树
java·算法·链表
灰色小旋风1 小时前
力扣20有效的括号(C++)
c++·算法·leetcode·职场和发展
逆境不可逃1 小时前
LeetCode 热题 100 之 160. 相交链表 206. 反转链表 234. 回文链表 141. 环形链表 142. 环形链表 II
算法·leetcode·链表
weiabc1 小时前
今日C/C++学习笔记20260223
c语言·c++·学习
CoovallyAIHub1 小时前
AAAI 2026 | 华中科大联合清华等提出Anomagic:跨模态提示零样本异常生成+万级AnomVerse数据集(附代码)
深度学习·算法·计算机视觉
博界IT精灵2 小时前
王道书3.4.3:特殊矩阵的压缩存储
数据结构·考研·矩阵
npupengsir2 小时前
nano vllm代码详解
人工智能·算法·vllm
m0_569881472 小时前
C++中的组合模式高级应用
开发语言·c++·算法
m0_730115112 小时前
高性能计算负载均衡
开发语言·c++·算法
busideyang2 小时前
STC8H单片机delay_ms函数闪烁不准?原因是参数溢出!
c语言·单片机·嵌入式硬件·嵌入式