【算法专题训练】33、堆

1、堆

最大堆与最小堆

  • 堆是一种特殊的树形数据结构,根据根节点的值与子节点的值的大小关系,堆又分为最大堆和最小堆。
  • 在最大堆中,每个节点的值总是大于或等于其任意子节点的值,因此最大堆的根节点就是整个堆的最大值
  • 在对小堆中,每个节点的值总是小于或等于其任意子节点的值,因为最小堆的根节点就是整个堆的最小值。
    堆通常用完全二叉树来实现

堆的操作

  • 完全二叉树可以使用数组来保存堆中结点,堆中根节点与左右子节点的关系,在数组中的下标存在一定逻辑关系
  • 往堆中添加新元素:通常是在堆的最后一个位置插入一个元素,然后判断该元素与父节点的大小关系,如果是最大堆,如果新插入的元素值比父节点的值要大,则要进行交换,这个操作会不断递归下去,直到父节点为根节点。
  • 在堆中删除元素,删除的元素是堆顶元素,如果是最大堆,则将堆中最下一层的最右边元素替换到根节点,并与左右子节点进行比较,如果存在左右子节点比父节点大的结点,则将较大的结点与父节点进行交换,并继续判断下去,直到没有子节点或父节点值最大为止。

C++中最大堆与最小堆的使用

cpp 复制代码
默认就是最大堆
priority_queue<int, std::vector<int>> maxHeap;
也可以指定:
priority_queue<int, std::vector<int>, std::less<int>> max_heap;    // 最大堆

最小堆:
priority_queue<int, std::vector<int>, std::greater<int>> min_heap; // 最小堆
也可以指定堆的判断方法

堆的应用

  • 堆经常用来求取一个数据集合中值最大和最小的k个元素。最小堆用来求取数据集合中k个值最大的元素,最大堆用来求取数据集合中k个值最小的元素。

2、LCR 059. 数据流中的第 K 大元素

题目信息:

cpp 复制代码
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:
KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

示例:
输入:
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
输出:
[null, 4, 5, 5, 8, 8]

解释:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3);   // return 4
kthLargest.add(5);   // return 5
kthLargest.add(10);  // return 5
kthLargest.add(9);   // return 8
kthLargest.add(4);   // return 8

提示:
1 <= k <= 104
0 <= nums.length <= 104
-104 <= nums[i] <= 104
-104 <= val <= 104
最多调用 add 方法 104 次
题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素

解题思路:

  • 1、审题:实现一个获取集合中第k大的值的一个类,在构造函数中输入数组nums和数值k
  • 然后调用add方法,往集合中插入一个元素val,并且该方法返回集合中的第k大的值
  • 2、解题:
  • 使用最小堆来求解集合中的最大值,priportyQueue,在构造函数中就开始组装数据,并保证最小堆只有k个元素
  • 调用add方法插入元素时,先判断最小堆的元素个数是否大于k,如果大于k,则先删除堆中最小元素,然后添加新元素,
    • 如果最小堆的元素个数小于k,则直接添加元素,最后返回最小堆的根节点元素值

代码实现:

cpp 复制代码
class KthLargest
{
public:
    KthLargest(int k, vector<int> &nums)
    {
        this->k = k;
        // 往最小堆中添加元素,直接调用add方法
        for (auto num : nums)
        {
            add(num);
        }
    }

    /**
     * 往最小堆中添加元素:
     * - 如果当前堆的元素个数小于k,则直接添加元素
     * - 如果当前堆的元素个数大于等于k了,则需要先判断新插入的元素如果大于最小堆顶元素,则堆顶元素出队再插入,否则不插入
     */
    int add(int val)
    {
        if (min_heap.size() < k)
        {
            min_heap.push(val);
        }
        else if (min_heap.top() < val)
        {
            min_heap.pop();
            min_heap.push(val);
        }
        return min_heap.top();
    }

private:
    priority_queue<int, std::vector<int>, std::greater<int>> min_heap; // 最小堆
    int k;
};

3、LCR 060. 前 K 个高频元素

题目信息:

cpp 复制代码
给定一个整数数组 nums 和一个整数 k ,请返回其中出现频率前 k 高的元素。可以按 任意顺序 返回答案。

示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:
输入: nums = [1], k = 1
输出: [1]

提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
进阶:所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

解题思路:

  • 1、审题:输入一个数组和k值,要求从数组中找出出现相同的数字,并且出现的次数排列在前k个的元素,并返回数组
  • 2、解题:
  • 要求数字出现的次数,需要使用哈希表map保存每个数字,和其对应出现的次数
  • 要求数字出现的最多的k次数,可以使用最小堆来比较元素出现的次数,次数比堆顶元素大的,则删除堆顶元素,并加入到最小堆中。如果比堆顶元素还小的,则直接过滤掉
  • 最后将最小堆中的元素添加到集合中返回

代码实现:

cpp 复制代码
static bool camp(std::pair<int, int> &m, std::pair<int, int> &n)
{
  return m.second > n.second;
}

vector<int> topKFrequent(vector<int> &nums, int k)
{
  std::map<int, int> map;
  // 将数组元素添加到map哈希表中
  for (auto num : nums)
  {
    if (map.find(num) != map.end())
    {
      // 存在
      map[num]++;
    }
    else
    {
      map[num] = 1;
    }
  }

  // 最小堆,需要保存键值对,使用pair作为堆中存储的元素
  priority_queue<pair<int, int>, std::vector<pair<int, int>>, decltype(&camp)> min_heap(camp);
  // 遍历哈希表map
  for (auto mapItem : map)
  {
    if (min_heap.size() < k)
    {
      min_heap.emplace(mapItem.first, mapItem.second);
    }
    else
    {
      // 比较最小堆的堆顶元素
      if (min_heap.top().second < mapItem.second)
      {
        min_heap.pop();
        min_heap.emplace(mapItem.first, mapItem.second);
      }
    }
  }

  vector<int> res;
  while (!min_heap.empty())
  {
    res.push_back(min_heap.top().first);
    min_heap.pop();
  }
  return res;
}

4、总结:

  • 堆这种结构的认识,本质上一棵完全儿叉树,又分为最大堆和最小堆,根据父节点值与左右子节点的值来判断的。
  • 注意堆数据结构的操作,删除堆顶元素和添加原始的操作
  • 堆结构的应用场景: 在一组数据集合中,求最大的k个元素,使用最小堆来保存数据,遍历集合,当堆中元素超过k个时,比堆顶元素大的情况下,则删除堆顶元素,并添加新元素,否则直接过滤。反之使用最大堆来保存数据。
相关推荐
Salt_07281 小时前
DAY25 奇异值SVD分解
python·算法·机器学习
℉AVE1 小时前
点集配准---迭代最近点算法ICP(Iterative Closest Point)
算法
大数据魔法师1 小时前
聚类算法(一)- K-Means聚类
算法·kmeans·聚类
CoderYanger1 小时前
A.每日一题——2141.同时运行N台电脑的最长时间
java·算法·leetcode·职场和发展·1024程序员节
Ayanami_Reii1 小时前
进阶数据结构-线段树
数据结构·算法·线段树
liu****1 小时前
11.字符函数和字符串函数(一)
linux·运维·c语言·开发语言·数据结构·算法
aini_lovee1 小时前
基于UERD算法的JPEG图像隐写MATLAB实现
开发语言·算法·matlab
橘颂TA2 小时前
【剑斩OFFER】算法的暴力美学——Z字行变换
算法·leetcode·职场和发展·结构与算法
minji...2 小时前
linux 进程控制(一) (fork进程创建,exit进程终止)
linux·运维·服务器·c++·git·算法