• 专题五:优先级队列


    "你了解我,最干净的轮廓, 握住小小风车和放肆的梦~" 


            堆是一个不错的数据结构,而在计算机中,无法表示二叉分支结构,因此我们经常会看到使用线性表来作为堆的存储容器。在接触堆的时候,我们是把它拿来同其他排序算法来看待的,但其实我们经常使用的是快排或者归并亦或者性能更加优越的"选择快排"。堆的应用场景,实质上转移到了查找问题,例如TopK等。很多语言也提供了以堆为底层的数据结构,例如C++中的priority_queue,Java中的PriorityQueue……

    如何实现一个堆排序?

            我们对任意一个堆的定义是一个完全二叉树,当一个父节点的值,大于左右子节点的值,则称为大堆,反之如果一个父节点的值,小于左右节点的值,则被称之为小堆。

            建堆的方法有两种,一种是"向上调整法",一种是"向下调整法"。前者的思想是着眼于整个数组,后者的思想着眼于分治,先将最小的子树进行一次堆排序,再不停向上迭代。因为"向下调整法"实现起来更为简单,并且效率更高,所以我们选择后者。

    (1) 构建一个堆(大堆)

    1. // 找到最后的父节点
    2. for (int i = (n - 1 - 1 ) / 2;i >= 0;--i)
    3. {
    4. adjustDown(nums, i);
    5. }
    6. void adjustDown(vector<int>& nums,int parent)
    7. {
    8. // 控制下标
    9. int n = nums.size();
    10. int child = parent * 2 + 1;
    11. while (child < n)
    12. {
    13. // 把更大的换上去
    14. if (child+1 < n && nums[child] < nums[child + 1]) child++;
    15. // 比较
    16. if (nums[parent] < nums[child]) {
    17. swap(nums[parent], nums[child]);
    18. // 更新
    19. parent = child;
    20. child = parent * 2 + 1;
    21. }
    22. else {
    23. // 结束
    24. break;
    25. }
    26. }
    27. }

    (2) 堆排序

            要实现"升序排序,构建大堆,反之构建小堆"(因为本篇不是着眼于堆排序,不解释)。

    1. void adjustDown(vector<int>& nums,int parent,int end)
    2. {
    3. // 控制下标
    4. int child = parent * 2 + 1;
    5. while (child < end)
    6. {
    7. // 把更大的换上去
    8. if (child+1 < end && nums[child] < nums[child + 1]) child++;
    9. // 比较
    10. if (nums[parent] < nums[child]) {
    11. swap(nums[parent], nums[child]);
    12. // 更新
    13. parent = child;
    14. child = parent * 2 + 1;
    15. }
    16. else {
    17. // 结束
    18. break;
    19. }
    20. }
    21. }
    22. void HeapSort(vector<int>& nums)
    23. {
    24. // 建堆
    25. int n = nums.size();
    26. // 找到最后的父节点
    27. for (int i = (n - 1 - 1 ) / 2;i >= 0;--i)
    28. {
    29. adjustDown(nums, i,n);
    30. }
    31. // 排序
    32. int end = n - 1;
    33. while (end > 0)
    34. {
    35. // 因为构建的是大堆,所有后面的数一定是小数
    36. swap(nums[end], nums[0]); // 同堆顶交换 让最大的数 在最后一个
    37. adjustDown(nums, 0,end);
    38. // 排序好一个数
    39. end--;
    40. }
    41. for (auto& n : nums)
    42. cout << n << " ";
    43. cout << endl;
    44. }

    ——前言


    1、最后一块石头的重量

    (1) 题目解析        

    (2) 算法原理        

    C++: 

    1. class Solution {
    2. public:
    3. int lastStoneWeight(vector<int>& stones) {
    4. // 寻找大数,构建大堆
    5. priority_queue<int,vector<int>,less<int>> heap;
    6. // 入队列
    7. for(auto& n:stones) heap.push(n);
    8. // 模拟出队列
    9. while(heap.size() > 1)
    10. {
    11. int x = heap.top();
    12. heap.pop();
    13. int y = heap.top();
    14. heap.pop();
    15. // 插入差值
    16. heap.push(x-y);
    17. }
    18. return heap.size() == 0 ? 0:heap.top();
    19. }
    20. };

    Java:

    1. class Solution {
    2. public int lastStoneWeight(int[] stones) {
    3. PriorityQueue heap = new PriorityQueue<>((a, b) -> b - a);
    4. for(int n:stones) heap.offer(n);
    5. // 模拟
    6. while(heap.size() > 1)
    7. {
    8. int a = heap.poll();
    9. int b= heap.poll();
    10. heap.offer(a-b);
    11. }
    12. return heap.isEmpty() ? 0 : heap.peek();
    13. }
    14. }

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

    (1) 题目解析

            这也是一个经典的topK问题,对于要找第K大的数,我们需要构建是小堆,而不是大堆。反之,要查找第K小的数,我们需要构建的是大堆,而不是小堆。

    (2) 算法原理

    c++: 

    1. class KthLargest {
    2. public:
    3. // 构建小堆
    4. priority_queue<int,vector<int>,greater<int>> _heap;
    5. int _k; // 构建多大的k
    6. KthLargest(int k, vector<int>& nums) {
    7. _k = k;
    8. // 入队列
    9. for(auto& n:nums)
    10. {
    11. _heap.push(n);
    12. if(_heap.size() > _k) _heap.pop(); // 剔除多余数
    13. }
    14. }
    15. int add(int val) {
    16. _heap.push(val);
    17. if(_heap.size() > _k) _heap.pop();
    18. return _heap.top();
    19. }
    20. };

     java:

    1. class KthLargest {
    2. PriorityQueue<Integer> heap;
    3. int _k;
    4. public KthLargest(int k, int[] nums) {
    5. _k = k;
    6. heap = new PriorityQueue<>(); // 默认是小堆
    7. for(int x:nums){
    8. heap.offer(x);
    9. if(heap.size() > _k) heap.poll();
    10. }
    11. }
    12. public int add(int val) {
    13. heap.offer(val);
    14. if(heap.size() > _k) heap.poll();
    15. return heap.peek();
    16. }
    17. }

    3、前K个高频单词

    (1) 题目解        

    (2) 算法原理   

    1. class Solution {
    2. public:
    3. typedef pair<int,string> PSI; // 频次与字符串 用于比较
    4. struct cmp
    5. {
    6. template<class T>
    7. bool operator()(T& t1,T& t2)
    8. {
    9. return (t1.first > t2.first) || (t1.first == t2.first) && (t1.second < t2.second);
    10. }
    11. };
    12. vector topKFrequent(vector& words, int k) {
    13. unordered_mapint> hash; // 统计频次
    14. for(auto& str:words) hash[str]++;
    15. // 普通的比较函数: less\greater 不能满足我们的要求
    16. // 所以我们得更换比较函数: 这里我们采用的是 仿函数
    17. priority_queue,cmp> heap;
    18. for(auto& n:hash)
    19. {
    20. heap.push({n.second,n.first});
    21. if(heap.size() > k) heap.pop();
    22. }
    23. // 倒序
    24. vector ret(k);
    25. for(int i=k-1;i>=0;--i)
    26. {
    27. ret[i] = heap.top().second;
    28. heap.pop();
    29. }
    30. return ret;
    31. }
    32. };


    4、数据流中的中位数

    (1) 题目解析        

            前两者解法都是很好想的,前提就是保证数组有序,再来找中位数。但,我们这里选择的解法不是其中的任意一种。

    (2) 算法原理        

    1. class MedianFinder {
    2. public:
    3. priority_queue<int,vector<int>,less<int>> _left;
    4. priority_queue<int,vector<int>,greater<int>> _right;
    5. MedianFinder() {}
    6. void addNum(int num) {
    7. if(_left.size() == _right.size())
    8. {
    9. if(_left.empty() || num <= _left.top())
    10. {
    11. // 直接进入
    12. _left.push(num);
    13. }
    14. else
    15. {
    16. // 替换
    17. _right.push(num);
    18. _left.push(_right.top());
    19. _right.pop();
    20. }
    21. }
    22. else
    23. {
    24. if(num <= _left.top())
    25. {
    26. _left.push(num);
    27. _right.push(_left.top());
    28. _left.pop();
    29. }
    30. else
    31. {
    32. _right.push(num);
    33. }
    34. }
    35. }
    36. double findMedian() {
    37. if(_left.size() == _right.size()) return (_left.top() + _right.top()) / 2.0;
    38. return _left.top(); // m=n+1
    39. }
    40. };

    本篇到此结束,感谢你的阅读。

    祝你好运,向阳而生~

  • 相关阅读:
    C/C++语言的服务器LS调研 (Language Server 实现代码索引 跳转定义 智能提示等功能)
    进阶JAVA篇-深入了解 Set 系列集合
    Java - 你真的明白单例模式怎么写了吗?
    数据结构 顺序表1
    百度元宇宙被“黑客”占领了
    【Python接口自动化】--深入了解HTTP接口基本组成和网页构建原理
    linux常用命令
    Redis发布订阅机制学习
    计算机毕业设计springboot进口零食销售网站74r3o源码+系统+程序+lw文档+部署
    Leetcode-234 回文链表
  • 原文地址:https://blog.csdn.net/RNGWGzZs/article/details/133408844