堆,堆构建,堆排序,PriorityQueue和TopN问题

news2025/2/24 9:33:40

零. 前言

        堆作为一种重要的数据结构,在面笔试中经常出现,排序问题中,堆排序作为一种重要的排序算法经常被问道,大顶堆小顶堆的应用经常出现,经典的问题TopN问题也是堆的重要应用,因此,了解并掌握这种数据结构是很必要的。

一. 堆的数据结构

1.由树而来

        堆的数据结构可以看作是一种由数组实现的抽象完全二叉树,通过大顶堆或者小顶堆,来达到快速找到一新数据在整个堆结构中的应有位置,继而来实现排序、TopN问题或者log级别的算法要求。

        完全二叉树,就是在一棵树中,元素从从上到下,从左到右依次变满,不会出现一个节点的左子节点不存在而右子节点存在的情况。

2.大顶堆 

        在一个大顶堆中,根节点的值比左右子树都要大(可以等于),在所有子树中都成立,但注意此时,左右子节点的大小并没有进行比较,所以是未知。

        一个典型的大根堆案例:

上图中的二叉树中的数值对应在数组中就是arr=[7,5,6,2,3,1,4],可以看作是树的层序遍历,从上到下,从左到右依次填满

3.小顶堆

         在一个小顶堆中,根节点的值比左右子树都要小(可以等于),在所有子树中都成立,但注意此时,左右子节点的大小并没有进行比较,所以是未知。

        案例同上,只不过大小相反,不再赘述。

4.数组实现堆中的对应关系

        由数组实现方法中,会有数组下标和堆的抽象完全二叉树的对应关系,即数组中的第i元素在堆的哪个位置,或者说,堆中的元素映射在数组是哪个位置。

        对于一个根节点i,它的左子节点是i*2+1,它的右子节点是i*2+2(如果存在的话,在实际中,可能左右子节点都不存在,会超出数组的实际长度,需提前判断)。

        在下方的案例,元素6的下标在数组中对应2,2*2+1=5,数组中5对应的元素是1,而右子节点的元素是4,是一一对应的,而如果知道左子节点,或者右子节点,也可以反过来求根节点的在数组中的位置,对于一个左右子节点i,(i-1)/2即是根节点。

 

二. 堆构建

1.堆的构建过程

            在手动构建堆的过程中,可以用以下的方式来手动构建一个堆【1】,默认为大顶堆:

    

 在整个堆的构建过程中,就是

(1)依次添加每个元素

(2)比较新添加的元素是否比根节点大(默认为大顶堆)

(3)如果新加入的子节点比根大就进行交换,然后一直向上换,直到不比父节点大,或者到达终点(在数组中即是0的位置)

        整个过程是从底向上的过程,可以理解为上窜,上浮过程

2.代码

class Solution {
    public int[] newarrtoeHeap(int[] nums) {
         return MakeHeap(nums);
    }    
    public int[] MakeHeap(int[] nums){
        int index = 0;
        for(int i = 0; i < nums.length; i++){
            HeapInsert(nums,index++);
        }
    }
    public void HeapInsert(int[] nums, int index){
        int root = (index-1)/2;
        while(nums[root] < nums[index]){
            swap(nums,root,index);
            index = root;
            root = (index-1)/2;
        }
    }
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

三. 利用堆进行排序

1.排序过程

        在第二章节中,对于一个已经构建好的堆来说,需要利用已经构建好的堆来进行排序,排序的整体过程如下:

        (1)对于已经构建好的大根堆来说,数组最左边的元素即是数组中的最大值,把它取出即可

        (2)然而取出之后,树不再是一颗完整的树,此时不利于后续操作(如果从左或者右取一个大元素放到根元素位置,那么后面的元素都要替换),所以此时可以把最左边的元素和最右边的元素交换,那么,最大的元素就是最右边的元素,缩减此时的有效区域即可

        (3)此时虚拟的完全二叉树的根元素是从数组最右边提取的,大小位置,此时三个元素(新根元素,左子节点,右子节点需要进行比较)选出最大值作为根元素,新根元素不断和左右子节点比较直到找到属于自己合适的位置

        (4)不断重复上述步骤,每次选出当前区间最大值,剔除最大值,区间从右向左不断缩减

利用第二章节的案例做具体的排序过程:

        

2.代码

class Solution {
    public int[] sortArray(int[] nums) {
         return HeapSort(nums);
    }    
    public int[] HeapSort(int[] nums){
        int index = 0;
        for(int i = 0; i < nums.length; i++){
            HeapInsert(nums,index++);
        }
        int sum = nums.length;
        swap(nums,0,--sum);
        while(sum > 0){
            Heaptify(nums,0,sum);
            swap(nums,0,--sum);
        }
        return nums;
    }
    public void HeapInsert(int[] nums, int index){
        int root = (index-1)/2;
        while(nums[root] < nums[index]){
            swap(nums,root,index);
            index = root;
            root = (index-1)/2;
        }
    }
    public void Heaptify(int[] nums,int temp, int sum){
        int left = temp * 2 +1;
        int right = left +1;
        while(left < sum){
            int max = right < sum ? (nums[left] < nums[right] ? right : left) : left;
            max = nums[max] < nums[temp]? temp : max;
            if(max == temp) break;
            swap(nums,max,temp);
            temp = max;
            left = temp * 2 +1;
            right = left +1;
        }
    }
    public void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

        以上的步骤都可以归结为:

        (1)交换(首尾交换)

        (2)下沉(新元素找到自己的位置)

        (3)重复交换和下沉步骤

以上的步骤在代码中体现为下跳,下沉过程,对应在代码中为Heaptify()方法。

        第二章节和第三章节的上浮和下沉操作配合即可完成排序过程。

3.堆排序的时间、空间复杂度、是否稳定

        堆排序的最好时间复杂度、最坏时间复杂度和平均时间复杂度都是O(nlog(n)),在构建堆和重构堆的过程中,寻找目标元素或者为目标元素寻找恰当位置都是翻层寻找(*2)。

        空间复杂度O(1),只需要额外的swap函数辅助即可。

        堆排序为不稳定排序,在重构堆的过程中会改变前后相同元素的原本位置。

四. Java PriorityQueue

      在Java中,一个堆可以用PriorityQueue类来实现,默认为小根堆,如果需要大根堆,可以进行重排序。

PriorityQueue<Integer> queue = new PriorityQueue<>();

重排序,可以用lambda表达式

PriorityQueue<Integer> queue = new PriorityQueue<>((v1,v2) -> v2-v1);

或者重写Comparator比较器 

    class minHeap implements Comparator<Integer>{
        public int compare(Integer m1,Integer m2){
            return m1 - m2;
        }
    }
    PriorityQueue<Integer> minheap = new PriorityQueue<>(new minHeap());
    
    class maxHeap implements Comparator<Integer>{
        public int compare(Integer m1,Integer m2){
            return m2 - m1;
        }
    }
    PriorityQueue<Integer> maxheap = new PriorityQueue<>(new maxHeap());

        具体使用,可以参考如下文章:

        数据结构_堆_Java中的实现类

五. 堆排序、topN、PriorityQueue相关问题

1. leetcode 912 排序数组

给你一个整数数组nums,请你将该数组升序排列。

输入:nums = [5,2,3,1]
输出:[1,2,3,5]

不再赘述,参考第三章节

2. 剑指 Offer 40. 最小的k个数

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

暴力解法

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] vec = new int[k];
        Arrays.sort(arr);
        for (int i = 0; i < k; ++i) {
            vec[i] = arr[i];
        }
        return vec;
    }
}

大根堆

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        int[] ans = new int[k];
        if(k == 0) return ans;
        PriorityQueue<Integer> queue = new PriorityQueue<>((v1,v2) -> v2-v1);
        for(int i : arr){
            if(queue.size() < k){
                queue.offer(i);
            }
            else{
                if(queue.peek() > i){
                    queue.poll();
                    queue.offer(i);
                }
            }
        }
        int index = 0;
        while(!queue.isEmpty()){
            ans[index++] = queue.poll();
        }
        // int idx = 0;
        // for(int num: queue) {
        //     ans[idx++] = num;
        // }
        return ans;
    }
}

本题小结:(1)此题是堆排序的典型应用案例,利用堆的性质来找到前k个大的数,或者前k个小的数

3. leetcode 347 前K个高频元素

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

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

class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
        HashMap<Integer,Integer> map = new HashMap();
        for(int num : nums){
            if (map.containsKey(num)) {
               map.put(num, map.get(num) + 1);
             } else {
                map.put(num, 1);
             }
        }
        // 遍历map,用最小堆保存频率最大的k个元素
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return map.get(a) - map.get(b);
            }
        });
        for (Integer key : map.keySet()) {
            if (pq.size() < k) {
                pq.add(key);
            } else if (map.get(key) > map.get(pq.peek())) {
                pq.remove();
                pq.add(key);
            }
        }
        // 取出最小堆中的元素
        List<Integer> res = new ArrayList<>();
        while (!pq.isEmpty()) {
            res.add(pq.remove());
        }
        return res;
    }
}

本题小结:(1)此题是和上体思路一样 ,不过需要首先求频率,在得到频率之后和上题解法如出一辙。

六. 参考来源 

【1】b站 左程云 一周刷爆LeetCode p5 

【2】leetcode yukiyama 十大排序从入门到入赘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/362114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kali Linux使用(含VMVare station player安装教程)

VMware Workstation Player下载及安装配置 1.官方下载地址&#xff1a;VMvare Workstation Player 2.安装&#xff1a;基本一路点&#xff0c;需要注意的地方就是后面弄好了要重启一下&#xff0c;记得保存文件 参考&#xff1a;https://www.bilibili.com/read/cv15292839…

Codeql 编译Shiro1.2.4爬坑

0x00 前言 这个Codeql一定要编译才能生成Database&#xff0c;是真的比较恼火&#xff0c;很多项目都不一定可以生成&#xff0c;环境就是一个非常大的坑&#xff0c;为了防止以后&#xff0c;所以将shiro1.2.4编译过程进行记录。 0x01 正文 首先是需要下载到shiro1.2.4的源…

音频(九)——I2S 输出正弦波

I2S 输出正弦波 PC 端&#xff1a;先生成一个正弦波数组MCU 端&#xff1a;将正弦波数组使用 I2S 输出AP 端&#xff1a;接受从 MCU I2S 端口出来的正弦波数据并测量 THDN 等数据 PC 端生成正弦波数组 原理 三角函数的公式 yAsinxy AsinxyAsinx A 表示幅值 代码实现 源…

TCP状态详解

TCP Tcp wrappers : Transmission Control Protocol (TCP) Wrappers 为由 inetd 生成的服务提供了增强的安全性。TCP Wrappers 是一种对使用 /etc/inetd.sec 的替换方法。TCP Wrappers 提供防止主机名和主机地址欺骗的保护。欺骗是一种伪装成有效用户或主机以获得对系统进行未…

线程的基本概念

文章目录基础概念线程与进程什么是进程&#xff1f;什么是线程&#xff1f;进程和线程的区别&#xff1a;多线程什么是多线程&#xff1f;多线程的局限性串行、并行、并发同步异步、阻塞非阻塞线程的创建1、继承Thread类&#xff0c;重写run方法2、实现Runnable接口&#xff0c…

Tomcat的类加载机制

不遵循双亲委托 在JVM中并不是一次性地把所有的文件都加载到&#xff0c;而是按需加载&#xff0c;加载机制采用 双亲委托原则&#xff0c;如下图所示&#xff1a; BootStrapClassLoader 引导类加载器ExtClassLoader 扩展类加载器AppClassLoader 应用类加载器CustomClassLoad…

位姿图优化(CeresG2OGTSAM)

0. 简介 作为SLAM中常用的方法&#xff0c;其原因是因为SLAM观测不只考虑到当前帧的情况&#xff0c;而需要加入之前状态量的观测。就比如一个在二维平面上移动的机器人&#xff0c;机器人可以使用一组传感器&#xff0c;例如车轮里程计或激光测距仪。从这些原始测量值中&…

Python用selenium实现自动登录和下单的脚本

前言 学python对selenium应该不陌生吧 Selenium 是最广泛使用的开源 Web UI&#xff08;用户界面&#xff09;自动化测试套件之一。Selenium 支持的语言包括C#&#xff0c;Java&#xff0c;Perl&#xff0c;PHP&#xff0c;Python 和 Ruby。目前&#xff0c;Selenium Web 驱动…

打游戏哪种蓝牙耳机比较好?适合玩游戏的无线蓝牙耳机

2023年耳机市场一如既往地卷&#xff0c;不只是卷音质&#xff0c;还在外观和功能上做了许多的改进&#xff0c;以至于现在哪怕不懂耳机的人从各电商平台都能闭眼入一个款平价品牌耳机且极少会踩雷&#xff0c;玩游戏是当前年轻人的娱乐方式&#xff0c;下面整理了几款适合玩游…

Git push报错DeployKey does not support push code

错误描述用Git从本地仓库上传服务器仓库报错&#xff1a;DeployKey does not support push code错误代码&#xff1a;(通过$ git push origin master命令从本地仓库上传到服务器仓库)错误原因&#xff1a;没有注册ssh公钥解决办法&#xff1a;添加ssh公钥&#xff1a;先生成对应…

滤波算法 | 无迹卡尔曼滤波(UKF)算法及其MATLAB实现

目录简介UKF滤波滤波流程和公式MATLAB程序结论简介 本文接着分享位姿跟踪和滤波算法中用到的一些常用程序&#xff0c;希望为后来者减少一些基础性内容的工作时间。以往分享总结见文章&#xff1a;位姿跟踪 | 相关内容目录和链接总结&#xff08;不断更新中~~~&#xff09; 本…

代码随想录算法训练营第六天 |哈希表理论基础、242.有效的字母异位词、349. 两个数组的交集 、202. 快乐数、 1. 两数之和

打卡第六天&#xff0c;补昨天的卡 今日任务 哈希表理论基础242.有效的字母异位词349.两个数组的交集202.快乐数1.两数之和 哈希表理论基础 哈希表是根据关键码的值而直接进行访问的数据结构。 哈希表能解决什么问题呢? 一般哈希表都是用来快速判断一个元素是否出现集合里。 …

JavaDoc生成API文档(powernode document)(内含源代码和导出的文档)

JavaDoc生成API文档&#xff08;powernode document&#xff09;&#xff08;内含源代码和导出的文档&#xff09; 源代码和导出的文档下载链接地址&#xff1a;https://download.csdn.net/download/weixin_46411355/87473296 目录JavaDoc生成API文档&#xff08;powernode do…

恺望数据:解决智驾数据生产痛点,提供自动化生产线和规模化人力

最近Chat GPT引起了一个热点话题&#xff0c;就是人工智能是否真的可以替代人类工作&#xff0c;特别是在需要进行数据标注等需要人力的领域。 自动驾驶数据服务公司恺望数据在最近的一个会议上透露了一些消息&#xff0c;他们已经推出了一个基于自动化的数据生产系统&#xff…

linux下安装minio

获取 MinIO 下载 URL:访问&#xff1a;https://docs.min.io/ 一&#xff0c;进入/opt 目录&#xff0c;创建minio文件夹 cd /optmkdir minio二&#xff0c;wget下载安装包 wget https://dl.minio.io/server/minio/release/linux-amd64/minio三&#xff0c;进入minio文件夹创建…

蓝海创意云vLive虚拟直播亮相2023昆山元宇宙产品展览会

2月15日-19日&#xff0c;由中国计算机行业协会“元宇宙创见未来”2023元宇宙产品展览会在江苏昆山隆重召开&#xff0c;共吸引了省内外32家企业参展&#xff0c;展出近百款元宇宙产品或技术&#xff0c;涵盖芯片、显示、VR、AR等硬件设备&#xff0c;以及工业、文旅、娱乐、教…

golang及goland的安装

1.电脑环境 2.软件下载 链接&#xff1a;https://pan.baidu.com/s/1YHM_jazftwkqRAuxJqMHZg 提取码&#xff1a;cdbm go1.17.5.windows-amd64.msi是go语言的开发及运行环境类似于Java的JDK。 goland-2020.2.2.exe 是go语言的开发工具(IDE),类似于Java的 Intelli J IDEA。 3…

【计算机网络】应用题方法总结

0.前言本篇博客主要记录自己在学习到的部分解决计算机网络应用题方法&#xff0c;主要参考视频如下&#xff1a;计算机网络期末复习 应用题_哔哩哔哩_bilibili【计算机网络】子网划分题型总结_哔哩哔哩_bilibili循环冗余码step 1&#xff1a;确定冗余码长度。多项式最高位即为冗…

阶段二11_面向对象高级_学生管理系统案例1

说明&#xff1a;学生管理系统案例需求和步骤1请查看上一张《阶段二10_面向对象高级_分类分包思想》 一.学生管理系统案例 步骤2&#xff1a;搭建主菜单和学生管理系统菜单 (0).主菜单和学习菜单界面和思路 界面&#xff1a; --------欢迎来到信息管理系统-------- 请输入您的…

CMake编译opencv4.6

openCV系列文章目录 文章目录openCV系列文章目录前言一、准备工作二、使用步骤1.使用CMake编译openCV总结前言 最近在项目中遇到图片处理&#xff0c;一拍脑袋就想到大名鼎鼎的opencv 一、准备工作 1.openCV官网下载 2.CMake官方下载 3.vs2019官方下载 二、使用步骤 1.使用…