手撕小顶堆

news2024/11/14 13:24:59

1. 抛砖引玉

在这里插入图片描述

给定两个以升序排列的整数数组 nums1 和 nums2 , 以及一个整数 k 。
定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。
请找到和最小的 k 个数对 (u1,v1), (u2,v2) … (uk,vk) 。

分析

大根堆(大顶堆):大根堆即指在逻辑上的二叉树结构中,根结点>子结点,总是最大的,并且在堆的每一个局部都是这样。

小根堆(小顶堆):与大根堆相反,即所有局部的根都小于子节点。
在这里插入图片描述
在java中优先队列是使用堆实现的。
主要有两个类

  1. PriorityQueue 线程不安全
  2. PriorityBlockingQueue 线程安全

我这里使用PriorityQueue,这一题我们可以通过一个大顶堆来维护一个答案序列

Jdk优先队列允许我们自定义比较器,可以比较的对象是可以使用优先队列的比较条件

PriorityQueue默认实现的是小顶堆,这里我们实现一个大顶堆

                PriorityQueue<List<Integer>> queue = new PriorityQueue<List<Integer>>(new Comparator<List<Integer>>() {
	            public int compare(List<Integer> m, List<Integer> n) {
	                if(m.get(0) + m.get(1) < n.get(0) + n.get(1)){
                        return 1;
                    }else{
                        return -1;
                    }
	            }
	        });

到这里,我们可以进行两次for循环

class Solution {
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        List<List<Integer>>result = new ArrayList<>();
        //构造有些队列,自定义比较器
                PriorityQueue<List<Integer>> queue = new PriorityQueue<List<Integer>>(new Comparator<List<Integer>>() {
	            public int compare(List<Integer> m, List<Integer> n) {
	                if(m.get(0) + m.get(1) < n.get(0) + n.get(1)){
                        return 1;
                    }else{
                        return -1;
                    }
	            }
	        });
	        //两次for循环,向优先队列中添加值
        for(int i = 0; i < nums1.length; i++){
            for(int j = 0; j < nums2.length; j++){
                if(queue.size() < k){
                    queue.offer(Arrays.asList(nums1[i],nums2[j]));
                }else{
                    if(nums1[i] + nums2[j] < queue.peek().get(0) + queue.peek().get(1)){
                        queue.poll();
                        queue.offer(Arrays.asList(nums1[i],nums2[j]));
                    
                    }else{
                        if(nums1[i] > queue.peek().get(0) && nums2[j] > queue.peek().get(1)){
                            break;
                        }
                    }
                }
            }
        }
        for(List<Integer>list : queue){
            result.add(list);
        }
        return result;

    }
}

可能有人要问了,两次for循环为什么我不直接暴力遍历

因为:我们维护了一个答案序列,知道最大或最小值,我们可以轻松淘汰一些无效不必要的遍历,例如:

当的数组1新的值和数组2新的值大于维护的答案队列中堆顶最大的值的时候,这次遍历就是不必要的,可以结束本次小循环。

if(nums1[i] > queue.peek().get(0) && nums2[j] > queue.peek().get(1)){
                            break;
                        }

2. 手撕堆排序

如果是当初,可能只会用 api 就够了,但是现如今,手撕堆排序也是不可或缺的技能了。

  1. 选择数据结构
    堆排序的最佳选项自然是数组,但是数组用起来并不好用,并不能动态扩容,我们这里使用同样底层是数据的数据结构 ArrayList
class Heap{
    int cap = 10;

    public Heap(int cap) {
        this.cap = cap;
    }
    ArrayList<Integer>list = new ArrayList<>();
    public void insert(int num){
    }
    public int peek(){
        return list.get(0);
    }
    private void bottomSort(int i){
  
    }
    private void topSort(int i){
     
    }

}

  1. 实现的函数

对外的函数主要有插入方法和返回堆顶堆方法,对内主要是两个排序方法,是上浮和下浮方法。

为什么有上浮和下浮两个方法?
我刚开始的时候只写了下浮方法,因为 前 k 大的算法题已经深深刻入我的脑海,但是随即就想到了一个问题,没加满的时候如何放置?

由于数组内的顺序很重要,不能随便移动,加入头部自然不可能,只能放到尾部,然后进行判值上浮,所以不得不同时实现两个方法,在数据不满的时候使用上浮方法,满了之后替换首个字符,使用下浮方法。

这里我实现小顶堆的方法,希望通用型更强的朋友可以试着使用关键字实现小顶堆和大顶堆两用的方法。

  1. 上浮方法
private void bottomSort(int i){
        while (i > 0){
            int iV = list.get(i);
            int lastIndex= (i - 1) / 2;
            int lastV = list.get(lastIndex);
            if(iV < lastV){
                list.set(i,lastV);
                list.set(lastIndex,iV);
            }
            i = lastIndex;


        }
    }

上浮方法比较简单,往往只用和父节点比较就可以,条件终止条件就是,i 已经交换到 0,即头节点。

  1. 下浮方法
private void topSort(int i){
        int cur = i;
        int iV = list.get(i);
        while (true){
            int left = 2 * i + 1;
            int right = 2 * i + 2;
            if(left < list.size() && list.get(cur) > list.get(left)){
                cur = left;
            }
            if(right < list.size() && list.get(cur) > list.get(right)){
                cur = right;
            }
            if(cur == i){
                break;
            }else {
                list.set(cur,iV);
            }
        }
    }

这里我们用了一个很巧妙的方法把大的数据下浮到较小的叶子上叶子上,实际上堆并没有如此严格,只保证相对有序就可以

插入方法和堆顶方法

 public void insert(int num){
        int size = list.size();
        if(cap == size){
            if(num <= peek()){
                return;
            }
            list.set(0,num);
            topSort(0);
        }else {
            list.add(num);
            bottomSort(size);
        }

    }
    public int peek(){
        return list.get(0);
    }

两个方法相对简单,就不多阐述了

3. 整体小顶堆代码

package 堆排序;

import java.util.ArrayList;

public class Solution {
    public static void main(String[] args) {
        Heap heap = new Heap(10);
        heap.insert(100);
        heap.insert(20);
        heap.insert(221);
        heap.insert(232);
        heap.insert(234);
        heap.insert(22);
        heap.insert(21);
        heap.insert(20);
        heap.insert(19);
        heap.insert(18);
        heap.insert(17);
        System.out.println(heap.peek());

    }

}
class Heap{
    int cap = 10;

    public Heap(int cap) {
        this.cap = cap;
    }

    ArrayList<Integer>list = new ArrayList<>();
    public void insert(int num){
        int size = list.size();
        if(cap == size){
            if(num <= peek()){
                return;
            }
            list.set(0,num);
            topSort(0);
        }else {
            list.add(num);
            bottomSort(size);
        }

    }
    public int peek(){
        return list.get(0);
    }
    private void bottomSort(int i){
        while (i > 0){
            int iV = list.get(i);
            int lastIndex= (i - 1) / 2;
            int lastV = list.get(lastIndex);
            if(iV < lastV){
                list.set(i,lastV);
                list.set(lastIndex,iV);
            }
            i = lastIndex;


        }
    }
    private void topSort(int i){
        int cur = i;
        int iV = list.get(i);
        while (true){
            int left = 2 * i + 1;
            int right = 2 * i + 2;
            if(left < list.size() && list.get(cur) > list.get(left)){
                cur = left;
            }
            if(right < list.size() && list.get(cur) > list.get(right)){
                cur = right;
            }
            if(cur == i){
                break;
            }else {
                list.set(cur,iV);
            }
        }
    }

}

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

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

相关文章

vue无法通过页面路径访问提示404,通过nginx配置处理

部署vue项目时&#xff0c;可以通过IP的方式访问主页&#xff0c;当进入特定页面在刷新时&#xff0c;因为浏览器通过URL地址进行请求&#xff0c;就提示404错误。 每次都需要重新从主页进入&#xff0c;这里是因为nginx配置的问题&#xff0c;在nginx里增加一行重定向的设置 …

如何使用淘宝API获取买家秀数据?一份详细指南

什么是淘宝买家秀API&#xff1f; 淘宝买家秀API是淘宝开放平台提供的一种接口&#xff0c;它允许开发者通过编程方式获取淘宝商品的买家秀信息&#xff0c;包括买家上传的图片、视频、评论等内容。 为什么需要使用淘宝买家秀API&#xff1f; 提升商品质量&#xff1a;通过分…

什么是CSRF攻击,该如何防护CSRF攻击

CSRF攻击&#xff08;跨站请求伪造&#xff0c;Cross-Site Request Forgery&#xff09;是一种网络攻击手段&#xff0c;攻击者利用已通过身份验证的用户&#xff0c;诱导他们在不知情的情况下执行未授权操作。这种攻击通常发生在用户登录到可信网站并且有活动的会话时&#xf…

【Elasticsearch】-spring boot 依赖包冲突问题

<dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.17.24</version></dependency> 在pom的配置中&#xff0c;只引入了elasticsearch-7.17.24 &#xff0c;但实际上会同时…

vue2中字符串动态拼接字段给到接口

【设计初衷是用户可根据给定的字段进行准确描述】 实现功能&#xff1a; 1. 文本域内容串动态配置字段&#xff0c;以$ {英文}拼接格式给到接口。 &#xff08;传参如&#xff1a;$ {heat_status_code}正常&#xff0c;$ {wdy_temp}也正常&#xff01;&#xff09; 2. 编辑时根…

Nginx从入门到入土(四):基于Nginx负载均衡策略

软负载和硬负载的概念前文提起过&#xff0c;那接下来我们便讲讲什么是负载均衡。 前言 Nginx负载均衡解决的是高并发的问题。 定义&#xff1a; 负载均衡&#xff08;Load Balancing&#xff09;是一种技术策略&#xff0c;它旨在将工作负载&#xff08;如网络流量、计算任…

react之jsx基础(2)高频使用场景

文章目录 1. **组件定义**2. **条件渲染**3. **列表渲染**4. **事件处理**5. **嵌套组件**6. **表单处理**7. **样式应用**8. **处理子组件** 在 React 中&#xff0c;JSX 的使用是非常广泛和高频的。以下是一些常见的高频使用场景及其示例&#xff0c;帮助你更好地理解 JSX 的…

Python基于flask框架的智能停车场车位系统 数据可视化分析系统fyfc81

目录 技术栈和环境说明解决的思路具体实现截图系统设计python语言django框架介绍flask框架介绍性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示技术路线操作可行性详细视频演示源码获取 技术栈和环境说明 结合用户的使用需求&…

我在高职教STM32——准备HAL库工程模板(2)

新学期已开始,又要给学生上 STM32 嵌入式课程了。这课上了多年了,一直用的都是标准库来开发,已经驾轻就熟了。人就是这样,有了自己熟悉的舒适圈,就很难做出改变,老师上课也是如此,排斥新课和不熟悉的内容。显然,STM32 的开发,HAL 库已是主流,自己其实也在使用,只不过…

基于SpringBoot+Vue的商场停车场管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

MeterSphere技术分享:UI自动化测试的必要性与解决方案

UI自动化测试体系的建设价值遵循测试金字塔模型&#xff0c;该模型建议测试人员在不同层次上编写和执行测试。UI测试需要编写和设计测试脚本、执行完整的应用程序&#xff0c;并模拟用户与应用程序交互&#xff0c;因此UI测试的测试速度相对较慢。但是UI测试的业务覆盖很高&…

【STL】stack,deque,queue 基础,应用与操作

stack 1.stack相关介绍 stack&#xff08;栈&#xff09; 栈是一种后进先出&#xff08;LIFO, Last In First Out&#xff09;的数据结构&#xff0c;意味着最后插入的数据最先被移除。C 中的栈是通过容器适配器实现的&#xff0c;底层默认使用 deque&#xff08;双端队列&am…

数字电子技术-数值比较器

目录 数值比较器 1、1位二进制数值比较器 多位数值比较器 4位数值比较器74LS85(TTL型) 74LS85数值比较器的使用说明 数值比较器 Digital Comparator,又称数字比较器&#xff0c;用以对两个数字的大小或是否相等进行比较的逻辑电路。 1、1位二进制数值比较器 A和B只能取值…

硬盘数据恢复必备:4 款强大硬盘数据恢复软件推荐!

在数字化的时代&#xff0c;我们的生活和工作越来越离不开电脑&#xff0c;而硬盘作为重要的数据存储设备&#xff0c;一旦出现数据丢失的情况&#xff0c;往往会给我们带来极大的困扰。别担心&#xff0c;今天就为大家推荐四款强大的硬盘数据恢复软件&#xff0c;帮助你轻松找…

六西格玛绿带培训多少钱?从授“鱼”到授“渔”

六西格玛作为一种全球公认的质量管理方法&#xff0c;其影响力日益扩大&#xff0c;而六西格玛绿带培训作为这一体系中的关键环节&#xff0c;更是吸引了众多希望在职场上脱颖而出的专业人士。本文&#xff0c;深圳天行健企业管理咨询公司将从多个维度深入探讨“六西格玛绿带培…

巴菲特的长期投资策略:新投资者实现财务自由的启示

在投资界&#xff0c;沃伦巴菲特的名字几乎无人不晓。作为伯克希尔哈撒韦公司的董事长和首席执行官&#xff0c;巴菲特以其卓越的投资智慧和长期价值增长策略&#xff0c;成为了全球投资者的偶像。巴菲特的成功不仅仅是因为他的财富&#xff0c;更在于他对投资的深刻理解和对财…

Linux嵌入式驱动开发指南(速记版)---Linux基础篇

第一章 Ubuntu系统入门 1.1 Linux磁盘管理 1.1.1 Linux磁盘管理基本概念 关键词&#xff1a; Linux 磁盘管理 挂载点 /etc/fstab文件 分区 ls /dev/sd* 联系描述&#xff1a; Linux 磁盘管理体系通过“挂载点”概念替代了 Windows 中的“分区”概念&#xff0c;将硬盘部分以文…

如何保养净水器

俗话说&#xff0c;“三分用&#xff0c;七分养”&#xff0c;这句话道出了家电这类消费品使用寿命的秘诀。家电的长久运行并不仅仅依赖于其出厂时的品质&#xff0c;更与我们日常的维护保养息息相关。虽然行业标准或制造商通常会规定家电的推荐使用年限&#xff0c;但在许多家…

【计算机网络】传输层协议UDP

目录 一、端口号1.1 端口号范围划分1.2 认识知名端口号 二、UDP协议2.1 UDP协议端格式2.2 UDP的特点2.3 UDP的缓冲区2.4 UDP使用注意事项2.5 基于UDP的应用层协议 一、端口号 传输层协议负责数据的传输&#xff0c;从发送端到接收端。端口号标识一个主机上进行通信的不同的应用…