欢迎光临
我们一直在努力

数据结构之树&&二叉树(三)

前言

堆是考研 408、算法面试里的高频考点,很多同学在这里踩坑:误以为建堆复杂度是\\(O(n\\log n)\\)、分不清 TopK 该用大根堆还是小根堆、不清楚堆排序为什么不稳定。本文从原理推导、算法流程、代码实现、易错点全方位讲解堆的三大核心考点。

建堆效率问题

1. 两种建堆方式(核心区分!)

方式 1:批量原地建堆(考研默认建堆,自底向上adjustDown向下调整)

操作流程 给定一个原始数组,我们直接把数组看作一棵完全二叉树。 从最后一个非叶子结点开始,向前遍历到根节点,对每一个节点执行向下调整siftDown操作。 向下调整:如果当前节点不满足堆性质(大根堆:父≥子),就和孩子中更大的节点交换,然后继续往下递归,直到满足堆性质。

方式 2:逐个插入建堆(自顶向上adjustUp向上调整)

从空堆开始,依次把数组每一个元素插入堆,每次插入放在数组末尾,执行向上调整。 每一次插入,最坏需要\\(\\log n\\)次调整,一共n个元素。

堆排序

1. 算法思想

堆排序利用大根堆堆顶永远是当前序列最大值这个特性。 整体分两大阶段:

  • 建堆阶段:对原始数组原地建大根堆,复杂度\\(O(n)\\)。数组第一个元素就是全局最大值。
  • 输出有序序列阶段 ① 交换堆顶(最大值)和堆末尾元素,最大值固定在数组尾部; ② 堆有效长度减 1(末尾已经排好,不再参与堆调整); ③ 对新的堆顶执行siftDown向下调整,重新维持大根堆; ④ 循环重复,直到堆里只剩一个元素。
  • 最终数组从前往后,从小到大有序。

    解决top  k问题

    1. 问题描述

    给定n个数字,找出前 K 大(或者前 K 小)的 K 个元素。

    重点场景:海量数据,n极大,无法一次性全部加载进内存,流式读取数据。

    需求堆类型说明
    求前 K 大元素 小根堆(容量 K) 堆顶是 K 个里面最小的,用来做门槛过滤
    求前 K 小元素 大根堆(容量 K) 堆顶是 K 个里面最大的,用来做门槛过滤

    以【求数组前 K 大元素】举例讲解:

  • 取前 K 个元素,构建容量为 K 的小根堆;堆顶是当前 K 个元素的最小值。
  • 遍历剩下的所有元素:
    • 如果当前元素 > 堆顶:说明它有资格进入 TopK。弹出堆顶,把当前元素入堆;
    • 如果当前元素 ≤ 堆顶:直接跳过,这个数不够大,进不了前 K。
  • 遍历结束,堆内保存的就是全部最大的 K 个元素,堆顶就是第 K 大的值。
  • 调试技巧,我们对创造的随机数是有范围的,那么我们之后直接在那个文件里面再某些数据后面加上一些0,就可以知道前几大的元素是什么

    void topk() {
    printf("请输入k:");
    int k = 0;
    scanf("%d", &k);
    const char* file = "data.txt";
    FILE* fout = fopen(file, "r");
    if (fout == NULL) {
    // 打开文件失败
    perror("fopen error");
    return;
    }
    ////////
    int val = 0;
    int* minheap = (int*)malloc(sizeof(int) * k);
    if (minheap == NULL) {
    // malloc失败
    perror("malloc error");
    return;
    }
    //将k个数据读到堆里面
    for (int i = 0; i < k; i++) {
    fscanf(fout, "%d", &minheap[i]);
    }
    //建立小堆
    for (int i = (k – 1 – 1) / 2; i >=0; i–) {
    AdjustDown(minheap, k, i);
    }
    int x = 0;
    while (fscanf(fout, "%d", &x) != EOF) {
    if (x > minheap[0])
    {
    minheap[0] = x;
    AdjustDown(minheap, k, 0);
    }
    }
    for (int i = 0; i < k; i++) {
    printf("%d ", minheap[i]);
    }
    free(minheap);
    fclose(fout);

    }

  • 先读取前 k 个数字,构建小根堆;堆顶是当前 k 个候选最大值里最小的那一个
  • 循环读取文件剩下每一个数字 x:
    • 如果x > 堆顶minheap[0],说明 x 有资格进入 TopK 集合,替换堆顶
    • 对堆顶执行AdjustDown,重新维护小根堆
  • 文件读完,堆内保存的就是整个文件中最大的 k 个数字
  • 赞(0)
    未经允许不得转载:171主机测评 » 数据结构之树&&二叉树(三)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址