【算法数据结构体系篇class07】:加强堆
一、手动改写堆(非常重要)!
系统提供的堆无法做到的事情:
1)已经入堆的元素,如果参与排序的指标方法变化,
系统提供的堆无法做到时间复杂度O(logN)调整!都是O(N)的调整!
2)系统提供的堆只能弹出堆顶,做不到自由删除任何一个堆中的元素,
或者说,无法在时间复杂度O(logN)内完成!一定会高于O(logN)
根本原因:无反向索引表
二、加强堆的核心点
1)建立反向索引表indexMap
2)建立比较器
3)核心在于各种结构相互配合,非常容易出错
三、构建加强堆演示
package class07;import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;/*** 手写一个加强堆,比系统自带的优先队列多了更多高效的获取元素查询动作* 注意泛型:T一定要是非基础类型,有基础类型需求包一层*/
public class HeapGreaterTrain<T> {//保存堆元素的集合public ArrayList<T> heap;//保存堆元素对应的索引位置 元素:索引public HashMap<T, Integer> indexMap;//堆大小public int heapSize;//比较器,自定义的比较器 ?匹配类型是T的父类 包括T本身public Comparator<? super T> comp;public HeapGreaterTrain(Comparator<? super T> c) {heap = new ArrayList<>();indexMap = new HashMap<T, Integer>();heapSize = 0;comp = c;}//判断堆是否为空public boolean isEmpty() {return heapSize == 0;}//判断堆大小public int size() {return heapSize;}//判断堆中是否包含某个元素public boolean contains(T obj) {return indexMap.containsKey(obj);}//查询堆顶元素public T peek() {return heap.get(0);}//入堆操作public void push(T obj) {//直接进集合最后,反向索引表创建关系,后面再接着insert向上排序heap.add(obj);indexMap.put(obj, heapSize);heapInsert(heapSize++);}//堆向上交换操作,默认是小根堆的排序private void heapInsert(int i) {//通过业务创建加强堆自定义的比较器来定义排序,假设返回小于0,执行交换操作while (comp.compare(heap.get(i), heap.get((i - 1) / 2)) < 0) {swap(i, (i - 1) / 2);i = (i - 1) / 2;}}//出堆操作public T pop(){//出堆顶,然后与最后一个元素交换,再将最后一个元素从集合与反向索引表删除T ans = heap.get(0);swap(0,--heapSize);heap.remove(heapSize);indexMap.remove(ans);heapify(0);return ans;}//堆向下沉操作 默认小根堆public void heapify(int i) {int left = i *2 +1;while(left < heapSize){int smallest = left+1 < heapSize && comp.compare(heap.get(left+1),heap.get(left))<0?left+1:left;smallest = comp.compare(heap.get(smallest),heap.get(i))<0?smallest:i;if(smallest == i) break;swap(smallest,i);i = smallest;left = i *2+1;}}//移除任意一个元素 将最后一个元素交换到该元素位置,然后再从该位置进行堆的上下调整排序操作public void remove(T obj){//获取集合中最后一个元素T end = heap.get(heapSize-1);//获取要删除元素的索引int index = indexMap.get(obj);//分别删除索引表该元素记录,与集合中最后一个元素,该元素前面已经保存下来indexMap.remove(obj);heap.remove(--heapSize);//注意:如果删除元素不是最后一个元素,再将该元素添加到index位置,并增加反向索引表记录if(obj != end){heap.set(index,end);indexMap.put(end,index);//调整完,就需要判断该元素是否需要上移或下移,其中只会执行一句resign(end);}}public void resign(T end) {heapInsert(indexMap.get(end));heapify(indexMap.get(end));}//返回堆上所有元素public List<T> getAllElements(){return heap;}private void swap(int i, int j) {//交换集合中的元素T o1 = heap.get(i);T o2 = heap.get(j);heap.set(i, o2);heap.set(j, o1);//重新设定反向索引表indexMap.put(o1, j);indexMap.put(o2, i);}
}
四、加强堆应用:用户颁奖问题
给定一个整型数组,int[] arr;和一个布尔类型数组,boolean[] op
两个数组一定等长,假设长度为N,arr[i]表示客户编号,op[i]表示客户操作
arr= [ 3 , 3 , 1 , 2, 1, 2, 5…
op= [T , T, T, T, F, T, F…
依次表示:3用户购买了一件商品,3用户购买了一件商品,1用户购买了一件商品,2用户购买了一件商品,1用户退货了一件商品,2用户购买了一件商品,5用户退货了一件商品…
一对arr[i]和op[i]就代表一个事件:
用户号为arr[i],op[i] == T就代表这个用户购买了一件商品
op[i] == F就代表这个用户退货了一件商品
现在你作为电商平台负责人,你想在每一个事件到来的时候,
都给购买次数最多的前K名用户颁奖。
所以每个事件发生后,你都需要一个得奖名单(得奖区)。
得奖系统的规则:
1,如果某个用户购买商品数为0,但是又发生了退货事件,
则认为该事件无效,得奖名单和上一个事件发生后一致,例子中的5用户
2,某用户发生购买商品事件,购买商品数+1,发生退货事件,购买商品数-1
3,每次都是最多K个用户得奖,K也为传入的参数
如果根据全部规则,得奖人数确实不够K个,那就以不够的情况输出结果
4,得奖系统分为得奖区和候选区,任何用户只要购买数>0,
一定在这两个区域中的一个
5,购买数最大的前K名用户进入得奖区,
在最初时如果得奖区没有到达K个用户,那么新来的用户直接进入得奖区
6,如果购买数不足以进入得奖区的用户,进入候选区
7,如果候选区购买数最多的用户,已经足以进入得奖区,
该用户就会替换得奖区中购买数最少的用户(大于才能替换),
如果得奖区中购买数最少的用户有多个,就替换最早进入得奖区的用户
如果候选区中购买数最多的用户有多个,机会会给最早进入候选区的用户
8,候选区和得奖区是两套时间,
因用户只会在其中一个区域,所以只会有一个区域的时间,另一个没有
从得奖区出来进入候选区的用户,得奖区时间删除,
进入候选区的时间就是当前事件的时间(可以理解为arr[i]和op[i]中的i)
从候选区出来进入得奖区的用户,候选区时间删除,
进入得奖区的时间就是当前事件的时间(可以理解为arr[i]和op[i]中的i)
9,如果某用户购买数==0,不管在哪个区域都离开,区域时间删除,
离开是指彻底离开,哪个区域也不会找到该用户
如果下次该用户又发生购买行为,产生>0的购买数,
会再次根据之前规则回到某个区域中,进入区域的时间重记
请遍历arr数组和op数组,遍历每一步输出一个得奖名单
publicList<List<Integer>> topK(int[] arr, boolean[] op, int k)
package class07;import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;public class Code02_EveryStepShowBoss {public static class Customer {public int id;public int buy;public int enterTime;public Customer(int v, int b, int o) {id = v;buy = b;enterTime = 0;}}public static class CandidateComparator implements Comparator<Customer> {@Overridepublic int compare(Customer o1, Customer o2) {return o1.buy != o2.buy ? (o2.buy - o1.buy) : (o1.enterTime - o2.enterTime);}}public static class DaddyComparator implements Comparator<Customer> {@Overridepublic int compare(Customer o1, Customer o2) {return o1.buy != o2.buy ? (o1.buy - o2.buy) : (o1.enterTime - o2.enterTime);}}public static class WhosYourDaddy {private HashMap<Integer, Customer> customers;private HeapGreaterTrain<Customer> candHeap;private HeapGreaterTrain<Customer> daddyHeap;private final int daddyLimit;public WhosYourDaddy(int limit) {customers = new HashMap<Integer, Customer>();candHeap = new HeapGreaterTrain<>(new CandidateComparator());daddyHeap = new HeapGreaterTrain<>(new DaddyComparator());daddyLimit = limit;}// 当前处理i号事件,arr[i] -> id, buyOrRefundpublic void operate(int time, int id, boolean buyOrRefund) {if (!buyOrRefund && !customers.containsKey(id)) {return;}if (!customers.containsKey(id)) {customers.put(id, new Customer(id, 0, 0));}Customer c = customers.get(id);if (buyOrRefund) {c.buy++;} else {c.buy--;}if (c.buy == 0) {customers.remove(id);}if (!candHeap.contains(c) && !daddyHeap.contains(c)) {if (daddyHeap.size() < daddyLimit) {c.enterTime = time;daddyHeap.push(c);} else {c.enterTime = time;candHeap.push(c);}} else if (candHeap.contains(c)) {if (c.buy == 0) {candHeap.remove(c);} else {candHeap.resign(c);}} else {if (c.buy == 0) {daddyHeap.remove(c);} else {daddyHeap.resign(c);}}daddyMove(time);}public List<Integer> getDaddies() {List<Customer> customers = daddyHeap.getAllElements();List<Integer> ans = new ArrayList<>();for (Customer c : customers) {ans.add(c.id);}return ans;}private void daddyMove(int time) {if (candHeap.isEmpty()) {return;}if (daddyHeap.size() < daddyLimit) {Customer p = candHeap.pop();p.enterTime = time;daddyHeap.push(p);} else {if (candHeap.peek().buy > daddyHeap.peek().buy) {Customer oldDaddy = daddyHeap.pop();Customer newDaddy = candHeap.pop();oldDaddy.enterTime = time;newDaddy.enterTime = time;daddyHeap.push(newDaddy);candHeap.push(oldDaddy);}}}}public static List<List<Integer>> topK(int[] arr, boolean[] op, int k) {List<List<Integer>> ans = new ArrayList<>();WhosYourDaddy whoDaddies = new WhosYourDaddy(k);for (int i = 0; i < arr.length; i++) {whoDaddies.operate(i, arr[i], op[i]);ans.add(whoDaddies.getDaddies());}return ans;}// 干完所有的事,模拟,不优化public static List<List<Integer>> compare(int[] arr, boolean[] op, int k) {HashMap<Integer, Customer> map = new HashMap<>();ArrayList<Customer> cands = new ArrayList<>();ArrayList<Customer> daddy = new ArrayList<>();List<List<Integer>> ans = new ArrayList<>();for (int i = 0; i < arr.length; i++) {int id = arr[i];boolean buyOrRefund = op[i];if (!buyOrRefund && !map.containsKey(id)) {ans.add(getCurAns(daddy));continue;}// 没有发生:用户购买数为0并且又退货了// 用户之前购买数是0,此时买货事件// 用户之前购买数>0, 此时买货// 用户之前购买数>0, 此时退货if (!map.containsKey(id)) {map.put(id, new Customer(id, 0, 0));}// 买、卖Customer c = map.get(id);if (buyOrRefund) {c.buy++;} else {c.buy--;}if (c.buy == 0) {map.remove(id);}// c// 下面做if (!cands.contains(c) && !daddy.contains(c)) {if (daddy.size() < k) {c.enterTime = i;daddy.add(c);} else {c.enterTime = i;cands.add(c);}}cleanZeroBuy(cands);cleanZeroBuy(daddy);cands.sort(new CandidateComparator());daddy.sort(new DaddyComparator());move(cands, daddy, k, i);ans.add(getCurAns(daddy));}return ans;}public static void move(ArrayList<Customer> cands, ArrayList<Customer> daddy, int k, int time) {if (cands.isEmpty()) {return;}// 候选区不为空if (daddy.size() < k) {Customer c = cands.get(0);c.enterTime = time;daddy.add(c);cands.remove(0);} else { // 等奖区满了,候选区有东西if (cands.get(0).buy > daddy.get(0).buy) {Customer oldDaddy = daddy.get(0);daddy.remove(0);Customer newDaddy = cands.get(0);cands.remove(0);newDaddy.enterTime = time;oldDaddy.enterTime = time;daddy.add(newDaddy);cands.add(oldDaddy);}}}public static void cleanZeroBuy(ArrayList<Customer> arr) {List<Customer> noZero = new ArrayList<Customer>();for (Customer c : arr) {if (c.buy != 0) {noZero.add(c);}}arr.clear();for (Customer c : noZero) {arr.add(c);}}public static List<Integer> getCurAns(ArrayList<Customer> daddy) {List<Integer> ans = new ArrayList<>();for (Customer c : daddy) {ans.add(c.id);}return ans;}// 为了测试public static class Data {public int[] arr;public boolean[] op;public Data(int[] a, boolean[] o) {arr = a;op = o;}}// 为了测试public static Data randomData(int maxValue, int maxLen) {int len = (int) (Math.random() * maxLen) + 1;int[] arr = new int[len];boolean[] op = new boolean[len];for (int i = 0; i < len; i++) {arr[i] = (int) (Math.random() * maxValue);op[i] = Math.random() < 0.5 ? true : false;}return new Data(arr, op);}// 为了测试public static boolean sameAnswer(List<List<Integer>> ans1, List<List<Integer>> ans2) {if (ans1.size() != ans2.size()) {return false;}for (int i = 0; i < ans1.size(); i++) {List<Integer> cur1 = ans1.get(i);List<Integer> cur2 = ans2.get(i);if (cur1.size() != cur2.size()) {return false;}cur1.sort((a, b) -> a - b);cur2.sort((a, b) -> a - b);for (int j = 0; j < cur1.size(); j++) {if (!cur1.get(j).equals(cur2.get(j))) {return false;}}}return true;}public static void main(String[] args) {int maxValue = 10;int maxLen = 100;int maxK = 6;int testTimes = 100000;System.out.println("测试开始");for (int i = 0; i < testTimes; i++) {Data testData = randomData(maxValue, maxLen);int k = (int) (Math.random() * maxK) + 1;int[] arr = testData.arr;boolean[] op = testData.op;List<List<Integer>> ans1 = topK(arr, op, k);List<List<Integer>> ans2 = compare(arr, op, k);if (!sameAnswer(ans1, ans2)) {for (int j = 0; j < arr.length; j++) {System.out.println(arr[j] + " , " + op[j]);}System.out.println(k);System.out.println(ans1);System.out.println(ans2);System.out.println("出错了!");break;}}System.out.println("测试结束");}}
相关文章:
【算法数据结构体系篇class07】:加强堆
一、手动改写堆(非常重要)!系统提供的堆无法做到的事情:1)已经入堆的元素,如果参与排序的指标方法变化,系统提供的堆无法做到时间复杂度O(logN)调整!都是O(N)的调整!2&am…...
Taro3.x 容易踩坑的点(阻止滚动穿透,弹框蒙层父级定位)
解决弹框滚动的时候,下层也会滚动问题》阻止滚动穿透(react,vue)案例描述:页面展示时需要滚动条才可以显示完整,但是当我们显示弹框的时候,即使不需要滚动条,但是页面仍然可以滚动,并且下层内容会随着滚动变…...
SpringBoot+ActiveMQ-发布订阅模式(消费端)
ActiveMQ消息中间件的发布订阅模式 主题 topictopic生产端案例(配合topic消费端测试):SpringBootActiveMQ Topic 生产端ActiveMQ版本:apache-activemq-5.16.5案例源码:SpringBootActiveMQ-发布订阅DemoSpringBoot集成ActiveMQ Topic消费端的pom.xml<?…...
vscode下使用arduino插件开发ESP32 Heltec WiFi_Kit_32_V3
下载vsCode 添加 arduino 插件 在Arduino IDE 中添加开发板,注意只能用右侧的开发板管理器添加,自己下载之后复制进去的IDE认,但是vsCode不认,搜索ESP32 第一个库里面只有到V2的,没有V3,要安装下面那个 H…...
吐血整理AutoSAR Com-Stack 的配置【基于ETAS】
总目录链接>> AutoSAR入门和实战系列总目录 文章目录01.软件组件和系统说明02.基本软件配置03.系统数据映射04.代码生成05.代码整合06.测试下图显示了基于 AUTOSAR 的 ECU SW 的结构。纵观BSW,大体分为三层。三层模块中,与通信相关的模块称为通信…...
面向对象进阶之元类
6. 元类 Python 中一切皆对象,对象是由类实例化产生的。那么类应该也有个类去产生它,利用 type() 函数我们可以去查看: class A:pass a1 A() print(type(a1)) print(type(A))<class __main__.A> <class type>由上可知…...
【Android AIDL之详细使用】
Android AIDL之详细使用一级目录概述使用场景语法相关编码实践服务端:java文件修改AndroidManifest客户端坑一级目录 概述 AIDL叫Android接口定义语言,是用于辅助开发者完成Android跨进程编程的工具。 从某种意义上说AIDL其实是一个模板,因…...
ASP.NET MVC | 简介
目录 前提 1.教程 2.MVC 编程模式 最后 前提 在学习学过很多课程,但是最主要学的还是ASP.NET MVC这门课程,工作也是用的ASP.NET MVC,所以写一点ASP.NET MVC的东西,大家可以来看看,我自己不会的时候也不用找别的地方…...
95后刚毕业2、3年就年薪50W,才发现,打败我们的不是年龄····
一刷朋友圈,一读公众号,一打开微博,甚至是一和朋友聊天,这些让人焦虑的话题总会铺天盖地的袭来: Ta刚毕业半年,就升职加薪当上了测试主管 (同样是一天24小时,为什么同龄人正在抛弃…...
动态分析和静态分析最主要的区别是什么?
动态分析和静态分析主要的区别是什么? 动态分析和静态分析的主要区别是是否考虑时间因素。 动态分析(dynamic analysis)是相对于静态分析来讲的,动态分析是只改变一下自变量,因变量相应的做出的改变,动态改…...
WebUI 学习笔记
WebUI 学习笔记 背景此插件主要用于在数字孪生方向做 UI 显示的效果。比如一些温度曲线需要显示出来,可以直接用插件,配合html 文件,直接显示出来。 准备工作我们采用4.27 版本进行开发;...
C# 中常见的设计模式附带代码案例
设计模式是一套被广泛应用于软件设计的最佳实践,它们可以帮助开发者解决特定的问题,提高代码的可重用性、可读性和可维护性。本文将介绍 C# 中常见的几种设计模式,并提供相应的示例代码。 工厂模式 工厂模式是一种创建型设计模式,…...
秋招面试问题整理之机器学习篇
文章目录随机森林在决策树的哪些方面做出了改进随机森林里每棵树的权重不一定会变成什么模型方差和偏差,正则化解决的是方差大还是偏差大的问题正则化的方法总结了解VC维吗svd了解吗随机森林在决策树的哪些方面做出了改进 回答思路: 随机森林和决策树有…...
SuperMap超图使用简单笔记
1 需求: 项目使用的是openlayer和Cesium,现在需要使用超图的图层,和引入实景公路功能。 2 使用过程中出现一下疑问点记录如下 : 超图: 北京超图软件股份有限公司是全球第三大、亚洲最大的地理信息系统(G…...
从0探索NLP——神经网络
从0探索NLP——神经网络 1.前言 一提人工智能,最能想到的就是神经网络,但其实神经网络只是深度学习的主要实现方式。 现在主流的NLP相关任务、模型大都是基于深度学习也就是构建神经网络实现的,所以这里讲解一下神经网络以及简单的神经网络…...
计算机操作系统和进程
✨个人主页:bit me👇 ✨当前专栏:Java EE初阶👇 ✨每日一语:心平能愈三千疾,心静可通万事理。 目 录🐬一. 操作系统🍦1. 操作系统是什么?🍨2. 操作系统的两个…...
JAVA服务端实现页面截屏(附代码)
JAVA服务端实现页面截屏适配需求方案一、使用JxBrowser使用步骤:方案二、JavaFX WebView使用步骤:方案三、Headless Chrome使用步骤:综上方案对比记录我的一个失败方案参考适配需求 有正确完整的地址url;通过浏览器能打开该url对…...
Java入门要知道!
首先我们都知道的是Java是一门面向对象的编程语言,不仅吸收了C语言的各种优点,还摒弃了C里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向…...
[6/101] 101次软件测试面试之经典面试题剖析
01、自我介绍答:大家好,我是一名软件测试工程师,但我更喜欢称自己为“软件bug捕手”。我相信,软件测试工程师的使命就是让软件更加健壮、更加可靠、更加美好。我们就像是一群“特警”,在黑暗的代码中寻找漏洞和缺陷&am…...
电脑c盘满了变成红色了怎么清理,清理c盘详细攻略
我们的电脑当用了一段时间之后,其实自然而然的就会有一点点卡,其实这是因为我们的电脑c盘满了,所以会造成卡顿是正常的,今天我们就来聊一聊电脑c盘满了变成红色了怎么清理? 一.电脑c盘为啥会满 软件安装:当…...
李慕婉-仙逆-造相Z-Turbo跨平台GUI开发:使用Qt构建模型调参与预览桌面应用
李慕婉-仙逆-造相Z-Turbo跨平台GUI开发:使用Qt构建模型调参与预览桌面应用 每次看到那些功能强大的AI模型,你是不是也心动过?但一打开命令行,面对密密麻麻的参数和代码,瞬间就觉得头大,只想关掉窗口。对于…...
Qwen3-ASR-1.7B部署教程:基于device_map=‘auto‘的GPU智能分配实践
Qwen3-ASR-1.7B部署教程:基于device_mapauto的GPU智能分配实践 想不想把电脑变成一个能听懂人话的智能助手?无论是会议录音、视频字幕,还是采访记录,都能快速、准确地转成文字,而且完全在本地运行,不用担心…...
OpenClaw数据清洗:GLM-4-7-Flash智能修复CSV文件常见问题
OpenClaw数据清洗:GLM-4-7-Flash智能修复CSV文件常见问题 1. 为什么需要自动化数据清洗工具 作为数据分析师,我每天要处理大量来源各异的CSV文件。最头疼的不是分析本身,而是前期数据清洗——编码混乱、日期格式不统一、缺失值扎堆…...
大模型学习6-模型量化与推理部署
LLM中的量化技术 本部分将系统介绍如何通过模型量化(Quantization)技术压缩LLM。首先,从量化背景出发,说明当前模型压缩的现实需求;其次,概述深度学习中的通用量化原理;最后,结合LL…...
OV7670 UART摄像头驱动开发:基于Camera_LS_Y201的嵌入式图像采集实现
1. Camera_LS_Y201 模块底层驱动技术解析Camera_LS_Y201 是一款基于 OV7670 图像传感器的低成本串口摄像头模组,其核心特征在于通过 UART 接口实现图像数据的一次性整帧传输(Bulk Transfer),而非传统逐行或分包发送方式。该方案由…...
非线性奇异谱分解算法:精细化处理时间序列数据,提取CSV文件信号特征,生成希尔伯特谱分析报告
SSD–fft–hht,奇异谱分解算法,是对原始小波分解的一种改进,对小波分解中的高频部分进行二次分解,提高分辨率。 一种非线性时间序列分解方法,可用于处理各种复杂数据,包括金融,气候,…...
LCDGraph:基于字符屏CGRAM的嵌入式轻量级实时绘图库
1. 项目概述LCDGraph 是一款专为嵌入式系统设计的轻量级图形绘制库,面向资源受限的微控制器平台(如 Arduino 系列),核心目标是在标准字符型 LCD 显示屏上实现高效、低开销的实时线性数据可视化。它不依赖图形点阵驱动或外部显存&a…...
RC522 RFID模块SPI驱动开发与寄存器级控制实践
1. RC522 RFID读写模块底层技术解析与嵌入式驱动开发实践1.1 模块硬件架构与通信协议基础RC522 是 NXP(恩智浦)推出的高度集成非接触式射频识别(RFID)读写芯片,广泛应用于门禁系统、公交卡读取、物流追踪等嵌入式场景。…...
Labview信号采集与分析系统:基础框架与二次开发的宝藏
Labview 信号采集与分析系统(含报告) 系统可作自己设计的基础框架,然后在基础上进行二次开发。 系统功能: (1)可采集传感器的真实信号; (2)可采集 labview 产生的模拟信号; (3&#…...
告别模拟音频线!用MAX98357A数字功放芯片,5分钟搞定I2S直连ESP32播放MP3
5分钟实现ESP32数字音频播放:MAX98357A功放芯片极简开发指南 在智能硬件开发中,音频输出功能常被视为"必要但麻烦"的组件——传统方案需要DAC转换、运放电路、滤波网络等一系列复杂设计。而MAX98357A这颗仅指甲盖大小的芯片,用纯数…...
