堆排序与TopK问题:核心总结与代码实现

堆排序与TopK问题:核心总结与代码实现

一、核心概念:堆的特性

  • 大顶堆:父节点≥子节点,堆顶是最大值
  • 小顶堆:父节点≤子节点,堆顶是最小值
  • 核心操作:向下调整算法(堆结构破坏后,通过父子节点交换恢复堆性质)

二、堆排序:全量数据排序

1. 核心逻辑

目标:将数组升序/降序排列,关键是“建堆+反复取极值归位”

2. 堆型选择

排序目标堆型原理
升序大顶堆堆顶是最大值,每次移到数组末尾归位
降序小顶堆堆顶是最小值,每次移到数组末尾归位

3. 步骤(升序为例)

  1. 用全部数据建大顶堆
  2. 交换堆顶(最大值)与未排序部分末尾元素,固定最大值
  3. 对剩余元素重新调整为大顶堆
  4. 循环至所有元素归位
void Swap(int& p1, int& p2)
{
    int tmp = p1;
    p1 = p2;
    p2 = tmp
}

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[parent] < a[child])//选大孩子,建大堆。选小孩子,建小堆。
        {
            Swap(a[child], a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

void HeapSort(int* a, int n)
{
    //建堆 -- 向下调整建堆 -- O[N]
    for (int i = (n - 1 - 1) / 2; i >= 0; i--)//(n-1)代表节点数,【(n-1)-1】是求父节点的公式
    {
        AdjustDown(a, n, i);
    }
    int end = n - 1;
    while (end > 0)
    {
        Swap(a[end], a[0]);
        AdjustDown(a, end, 0);
 
        --end;
    }
}

4. 关键指标

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(1)(原地排序)

三、TopK问题:海量数据极值筛选

1. 核心逻辑

目标:从海量数据中找前K大/前K小元素,关键是“用K容量堆筛选,不排序全部数据”

2. 堆型选择

目标需求堆型(容量=K)原理
前K大小顶堆堆顶是候选K元素的最小值,新元素>堆顶则替换并调整
前K小大顶堆堆顶是候选K元素的最大值,新元素<堆顶则替换并调整

3. 步骤(前K大为例)

  1. 取前K个数据建小顶堆
  2. 遍历剩余数据:新元素>堆顶则替换堆顶,重新调整小顶堆
  3. 遍历结束,堆内元素即前K大

4. 关键优势

  • 时间复杂度:O(n log K)(远优于全量排序)
  • 空间复杂度:O(K)(适合海量数据/内存有限场景)

四、堆排序 vs TopK:核心差异

对比维度堆排序TopK问题
目标全量排序找前K个极值
堆容量动态减小(=未排序数据长度)固定为K
核心操作取堆顶归位+调堆筛选元素+调堆
时间复杂度O(n log n)O(n log K)

五、TopK问题代码实现(找前K大)

1. 向下调整算法(小顶堆)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>

void Swap(int* a, int* b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

// 构建小顶堆:child选较小子节点,小于父节点则交换
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]) {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        } else break;
    }
}

2. 生成测试数据(可选)

// 生成n条随机数据存入data.txt
void CreateNData(int n) {
    srand(time(0));
    FILE* f = fopen("data.txt", "w");
    if (!f) { perror("fopen err"); return; }
    for (int i = 0; i < n; i++) fprintf(f, "%d\n", rand() % 10000);
    fclose(f);
}

3. TopK筛选主逻辑

// 从文件中找前k大元素
void PrintTopK(const char* file, int k) {
    // 1. 初始化k容量小顶堆
    int* topk = (int*)malloc(sizeof(int)*k);
    assert(topk);
    FILE* f = fopen(file, "r");
    if (!f) { perror("fopen err"); free(topk); return; }
    
    // 读前k个元素建堆
    for (int i = 0; i < k; i++) fscanf(f, "%d", &topk[i]);
    for (int i = (k-2)/2; i >= 0; i--) AdjustDown(topk, k, i);
    
    // 2. 筛选剩余数据
    int val;
    while (fscanf(f, "%d", &val) != EOF) {
        if (val > topk[0]) { // 新元素大则替换堆顶
            topk[0] = val;
            AdjustDown(topk, k, 0);
        }
    }
    
    // 3. 打印结果
    printf("前%d大元素:", k);
    for (int i = 0; i < k; i++) printf("%d ", topk[i]);
    printf("\n");
    
    free(topk);
    fclose(f);
}

4. 主函数调用

int main() {
    // CreateNData(10000000); // 生成1000万条测试数据(仅需1次)
    PrintTopK("data.txt", 10); // 找前10大元素
    return 0;
}

六、关键总结

  • 堆排序:用对应堆型(升序大顶堆)实现全量有序,适合数据量适中场景
  • TopK:用反向堆型(前K大小顶堆)筛选极值,适合海量数据/内存有限场景
  • 核心工具:向下调整算法(堆操作的基础)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值