2023.4.19 + 4.20
文章目录
- String类
-
- 1:介绍:
- 2:String类实现了很多的接口:
- 3;String类常用构造器
- 4:不同方式创建String类对象的区别
-
- (1)直接赋值的方式
- (2)常规new的方式
- (3)字符串拼接形式
- (4)有变量参与的字符串拼接
- 5:String类常用的方法
-
- (1):charAt( )
- (2):equals( )
- (3):重写的compareTo方法
- (4):substring
- (5):concat
- (6):replace
- (7):split
- (8):toUpperCase
- (9):toLowerCase
- (10):trim
- (11):toString
- (12):valueOf
- StringBuilder StringBuffer
-
- 1:介绍
- 2:构造器
- 做题小技巧:判断时间复杂度的
- 线段最大重合问题
- 加强堆的应用
-
- 暴力解法
- 大根堆解法:
- Heapgreater
String类
https://blog.csdn.net/TYRA9/article/details/129348766
1:介绍:
每一个字符串对象都是常量。字符串中的字符使用unicode编码,一个字符(不区分中英文)均占两个字节。java中用String类描述字符串,如下图所示 :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fmKscVlE-1681998609040)(D:/%E4%BD%A0%E5%A5%BDJava/1087.png)]
String类用final关键字修饰,因此String类是不可被其他类继承的;并且,通过String类中的属性value,我们可以得知String类的底层实际其实是字符数组char[ ] 。也就是说,new出的String对象在堆空间中,该对象的空间里有value引用变量指向常量池中一数组,用于存放字符串的内容(实际是指向常量池的地址)。该value数组也使用了final关键字修饰,当final关键字修饰引用类型时,不可更改其引用的指向,即不能更改其保存的地址值。
2:String类实现了很多的接口:
其中最重要的是以下两个
*Serializable接口*,实现该接口使得String类型可串行化,串行化后,String类型可以进行网络传输。
*Comparable接口*,实现该接口使得String类型可以进行“比较”的操作。
3;String类常用构造器
// 1.String():该构造器可以初始化一个String对象,使其指向一个空字符序列。 String str1 = new String(); System.out.println(str1); // // 2.String(byte[] bytes):该构造器可以初始化一个String对象,并将指定字节数组中的数据转化成字符串。 byte[] bytes = {65, 66, 67, 68}; String str2 = new String(bytes); System.out.println(str2); // ABCD // 3.String(char[] value):该构造器可以初始化一个String对象,并将指定字符数组中的数据转化成字符串。 char[] value = {'C', 'y', 'a', 'n', '_', 'R', 'A', '9'}; String str3 = new String(value); System.out.println(str3); // Cyan_RA9 // 4.String(char[] value, int offset, int count):该构造器可以初始化一个String对象,并将指定字符数组中的指定数据 转化成字符串。 String str4 = new String(value, 0, 4); System.out.println(str4); // Cyan // 5.String(String original):该构造器可以初始化一个String对象,使该对象实际指向的字符串常量与传入的字符串形参相 同,相当于是形参的一份拷贝。 String str5 = new String("CSDN yyds!"); System.out.println(str5); // CSDN yyds!
4:不同方式创建String类对象的区别
(1)直接赋值的方式
String str_0 = "Cyan"
String类在实际开发中的使用场景非常多,java在底层提供了针对String类的优化,即可以不通过构造器来创建String对象。而是直接赋值一个字符串。
String str_0 = “Cyan”; ,使用这种方式来创建String对象,jvm会先从常量池中查看是否有"Cyan"字符串的数据空间,若有,令引用变量直接指向该空间;若无,重新创建"Cyan"的数据空间并令其引用变量指向它。因此,str_0引用最终直接指向的是常量池中"Cyan"常量的空间地址。
(2)常规new的方式
String str_1 = new String("Cyan");
使用这种方式来创建String对象,jvm会先在堆空间中给String对象开辟空间,这片空间中维护了value数组这一属性,该属性指向常量池的"Cyan"数据空间。若常量池中没有"Cyan",则重写创建"Cyan"数据空间,并让value引用指向该数据空间。最后,将该对象堆空间的地址值返回给str_1引用。因此,str_1引用最终直接指向的是堆中的空间地址。
(3)字符串拼接形式
String str_0 = "Cyan" + "RA9";
若使用字符串拼接的方式来创建字符串对象,如下 :
当我们以两个字符串拼接的形式来创建字符串对象时,jvm不会在常量池中分别创建"Cyan"字符串常量和"RA9"字符串常量,再把它们拼接起来。而是直接在底层优化,先把两个字符串拼接起来,之后直接去常量池中寻找有没有拼接起来的这个字符串常量,如果有,直接令该引用指向这个字符串常量的空间地址;如果没有则先创建该字符串常量,再让引用指向这里
(4)有变量参与的字符串拼接
String a = "abc"; String b = a + "def"; // 创建一个StringBuilder对象,然后对"abc"进行拼接,再对"def"进行拼接,最后将 StringBuilder调用toString转换成String类型赋给b. System.out.println(b);
a变量在编译的时候不知道a是“abc”字符串,所以不会进行编译期优化,不会直接合并为“abcdef”
5:String类常用的方法
(1):charAt( )
根据字符串的下标找字符
String z = new String("abcdef"); char y = z.charAt(1); System.out.println(y); // b
(2):equals( )
比较的是两个字符串里面的内容是否是一样的。会区分大小写。
(3):重写的compareTo方法
会发现:只有两个字符串完全相等的时候才会返回0,返回是正还是负有点字典序的意思。
(4):substring
字符串的截取
String s = "abcdefghigklmnopqrst"; System.out.println(s.substring(5)); // [5 , s.length) System.out.println(s.substring(3,8)); // [3 , 8) // fghigklmnopqrst // defgh
(5):concat
字符串的拼接
String s = "abcdefghigklmnopqrst"; System.out.println(s.concat("ppp")); // abcdefghigklmnopqrstppp
(6):replace
字符串中的字符替换
String str = "abcdeabcdeabcdeabcde"; System.out.println(str.replace("a", "w")); // 把str中的a替换成w // wbcdewbcdewbcdewbcde
(7):split
按照指定的字符串进行分裂为数组的形式:
String str = "a-b-c-d-e-f-g-h-i-k"; String[] nub1 = str.split("-"); System.out.println(Arrays.toString(nub1)); // [a, b, c, d, e, f, g, h, i, k] String str3 = "a c n u"; String[] nub2 = str3.split(" "); System.out.println(Arrays.toString(nub2)); // [a, c, n, u] String str4 = "abc cba mba obuuu"; String[] nub3 = str4.split("b"); System.out.println(Arrays.toString(nub3)); // [a, c c, a m, a o, uuu]
(8):toUpperCase
将字符串转化成大写
String str = "a-b-c-d-e-f-g-h-i-k"; System.out.println(str.toUpperCase()); // A-B-C-D-E-F-G-H-I-K
(9):toLowerCase
将字符串转成小写
(10):trim
去除字符串的首尾空格
String str3 = " a c n u "; System.out.println(str3.trim()); //a c n u
(11):toString
将字符串以字符串的形式打印,返回值还是String类型
String str3 = " a c n u "; System.out.println(str3.toString()); // a c n u
(12):valueOf
转换为String类型,注意这是static修饰的一个类方法
char[] arr = {98,99,100}; System.out.println(String.valueOf(arr)); // bcd
StringBuilder StringBuffer
https://blog.csdn.net/TYRA9/article/details/129368975
1:介绍
在String类中,我们提到,每个字符串对象都是常量。当我们创建一个字符串对象,并试图对其内容进行“增”,“删”,或者“改”的操作时,实际上原来的字符串对象已经丢弃了。jvm会重新创建一个字符串对象,并令其指向常量池中新的数据空间。所以,如果多次进行这些“增删改”的操作,会导致大量副本字符串对象遗留在内存中,降低效率。那我们如何解决这个问题?这便要引出我们的StringBuffer类和StringBuilder类。
StringBuffer类,指可变字符序列,用于构造字符串对象。其内部使用自动扩容的数组来操作字符串数据
StringBuffer继承的父类是AbstractStringBuilder。其存储字符的数组是value数组,该数组无final修饰! 因此数组的引用是可以发生变化的。字符串存放的位置是在堆内存中。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r4SHukUr-1681998609043)(D:/%E4%BD%A0%E5%A5%BDJava/1100.png)]
注意这里面的额count
2:构造器
当使用无参构造器进行构造时,默认开辟的数组的大小是16.
当使用有参构造器进行构造时,开辟的数组的大小是你传入的值。
当传入的是字符串的时候,会开辟一个数组大小为str.length+16的数组,然后将传进来的字符串的内容拼接在后面。
你会发现value的地址一直发生改变。
文章目录
- String类
-
- 1:介绍:
- 2:String类实现了很多的接口:
- 3;String类常用构造器
- 4:不同方式创建String类对象的区别
-
- (1)直接赋值的方式
- (2)常规new的方式
- (3)字符串拼接形式
- (4)有变量参与的字符串拼接
- 5:String类常用的方法
-
- (1):charAt( )
- (2):equals( )
- (3):重写的compareTo方法
- (4):substring
- (5):concat
- (6):replace
- (7):split
- (8):toUpperCase
- (9):toLowerCase
- (10):trim
- (11):toString
- (12):valueOf
- StringBuilder StringBuffer
-
- 1:介绍
- 2:构造器
- 做题小技巧:判断时间复杂度的
- 线段最大重合问题
- 加强堆的应用
-
- 暴力解法
- 大根堆解法:
- Heapgreater
做题小技巧:判断时间复杂度的
线段最大重合问题
package algorithmbasic.class8;import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;public class CoverMax {// 方法一public static int maxCover1(int[][] lines) {int min = Integer.MAX_VALUE;int max = Integer.MIN_VALUE;// 找出所有线段的最小起始位置// 找出所有线段的最大终止位置for (int i = 0; i < lines.length; i++) {min = Math.min(min, lines[i][0]);max = Math.max(max, lines[i][1]);}// 线段的最小起始位置min与最大终止位置max已找到。// 在[min , max]范围上寻找每一个0.5隔断会穿过多少线段数,找出最大的即答案int cover = 0;for (double i = min + 0.5; i < max; i += 1) {int count = 0;for (int j = 0; j < lines.length; j++) {if (i > lines[j][0] && i < lines[j][1]) {count++;}}cover = Math.max(count, cover);}return cover;}// 方法二// 1:找出所有线段的最小起始位置min和最大终止位置max// 2:根据线段的起始位置对线段进行排序。// 3:问题转化:求某一区间线段重合的最大条数 --> 因为重合最多的区间其起始位置与终止位置一定是某个线段的起始位置与终止位置 -->// -> 求每个线段起始位置被穿过的次数。public static int maxCover2(int[][] lines) {int min = Integer.MAX_VALUE;int max = Integer.MIN_VALUE;// 找出所有线段的最小起始位置// 找出所有线段的最大终止位置Line[] line = new Line[lines.length];for (int i = 0; i < lines.length; i++) {line[i].start = lines[i][0];line[i].end = lines[i][1];}// 根据线段的起始位置对所有的线段进行排序Arrays.sort(line, new MyCompoter());// 默认是小根堆PriorityQueue<Integer> heap = new PriorityQueue<>();int cover = Integer.MIN_VALUE;for (int i = 0; i < line.length; i++) {while (!heap.isEmpty() && line[i].start >= heap.peek()) {heap.poll();}heap.add(line[i].end);cover = Math.max(cover, heap.size());}return cover;}public static class MyCompoter implements Comparator<Line> {@Overridepublic int compare(Line o1, Line o2) {return o1.start - o2.start;}}public static class Line {int start;int end;public Line(int start, int end) {this.start = start;this.end = end;}}
}
加强堆的应用
题目要求:
做一个加强堆的题目,给定一个整型数组,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数组,遍历每一步输出一个得奖名单
public List<List> topK (int[] arr, boolean[] op, int k)
// 暴力解法思路:
// 1:根据题目要求我们可以得出,用户对象Customer有属性buy(购买数量),enterTime(进入时间),id(独自的ID)这三个属 性,与此同时我们还分析出有得奖区和等候区两个区域,即两个容器。返回的结果是List<List<Integer>>类型,即一个链表, 链表中存储的一个存储类型是Integer类型的一个链表,记录每一次的TOPK的顾客的ID。
// 2:根据题意我们知到:当没有这个人时,这个人还退货,那么直接跳过,进行下一次的操作。这里出现了一个问题,我们如何知到 有没有这个人,因为传进来的参数只有(ID, buyOrReund, k),难道我们要遍历那两个容器吗,时间复杂度太高,所以我们需要 根据ID查找有没有这个人。所以我们可以写一个hashmap<Integer , Customer>,这样可以快速的查找又没有这个人,并且可 以快速的拿到这个人,需要注意是,hashmap中村的顾客是两个容器中所有buy!= 0的人。
// 3:2已经分析了一种情况:当没有这个人时,这个人还退货,还剩三种情况,(1):当没有这个人时,这个人还买货。(2):有这个 人时,这个人还买货。(3):有这个人时,这个人还退货。
// 4:(1): 创建这个顾客,Customer c = new Customer(id, i, 1);其进入的时间是现在的时间。因为一次只能买一个,所以buy == 1,Hashmap中存一下,之后这个顾客放到那个容器中呢?如果终中奖区没满,就将其进中奖区。如果中奖区满了就将其放进 等待区,放完之后我们再调整两个区域,调正的准则是:中奖区最差的放在头,等候区最好的放在头。之后必将两个头,如果等 候区的头的buy大于中奖区头的buy就交换。
// (2):如果有这个人这个人还买货,或者是有这个人时,这个人还退货。我们可以通过hashmap根据这个人的ID迅速的得到这个 Customer,对其buy进行调整,c.buy++ or c.buy--,如果调整完以后他的buy == 0,那么就将这个对象删除。hashmap中删除 与此同时容器中的这个对象也要删除。由于不知到这个对象在那个容器中,所以两个容器都要删除一遍cleanZeroBuy(cands); cleanZeroBuy(daddy);删完之后,或者是说buy调整完之后,两个容器需要调整。中奖区最差的放在头,等候区最好的放在 头。之后必将两个头,如果等候区的头的buy大于中奖区头的buy就交换。
// 最后生成TOPOK。// 大根堆解法思路:
// 1:暴力解法中,使用的容器是Arraylist类型,其进行排序调整的时间复杂度是:NlogN,删除的时间复杂度是N,由于一共有N个数 据所以总的时间复杂度是: N^2logN.
// 2: 容器我们可以使用加强堆,这样调整的时间复杂度是logN,删除的时间复杂度也是logN,而且堆的调整我们可以自己写比较器,按 照自己的要求调整堆。总的时间复杂度降到NlogN
// 3:注意java中一般的堆,你只能存与取,非常的单一。不可以对堆中的元素进行修改,一修改就废了。因为他没法自己调整成正确 的堆。但是加强堆不同,她可以对堆中的元素进行修改,它可以自己调整成正确的堆。
// 大根堆的好处:可以对堆中的元素进行修改,它可以自己调整成正确的堆,并且调整的时间复杂度是logN级别的非常的高效,与此同 时能传入比较器,按照自己想要的规则进行tiao'zheng。
暴力解法
package algorithmbasic.class8;import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;public class EveryStepShowBoss2 {// 顾客内部类public static class Customer {public int id;public int enterTime;public int buy;public Customer(int id, int enterTime, int buy) {this.id = id;this.enterTime = enterTime;this.buy = buy;}}// 属性// 存储返回值List<List<Integer>> ans = new ArrayList<>();// 得奖区ArrayList<Customer> daddy = new ArrayList<>();// 等候区ArrayList<Customer> cands = new ArrayList<>();// 查找是否有这个顾客 --> (根据id找customer)HashMap<Integer, Customer> map = new HashMap<>();// 方法public List<List<Integer>> topK2(int[] arr, boolean[] op, int k) {for (int i = 0; i < arr.length; i++) {int id = arr[i];boolean buyOrRefund = op[i];// 没有这个顾客并且退货if (!map.containsKey(id) && !buyOrRefund) {ans.add(this.getCurAns());continue;}// 没有这个顾客 -> 买// 有这个顾客 -> 退// 有这个顾客 -> 买// 这个是:没有这个顾客 -> 买if (!map.containsKey(id)) {Customer c = new Customer(id, i, 1);map.put(id, c);// cands / daddy 中加if (daddy.size() < k) {daddy.add(c);} else {cands.add(c);}// 后面会涉及一个调整} else { // 有这个顾客 -> 退 有这个顾客 -> 买Customer c = map.get(id);if (buyOrRefund) {c.buy++;} else {c.buy--;}if (c.buy == 0) {map.remove(id);// 只会执行其中一个// NcleanZeroBuy(cands);cleanZeroBuy(daddy);}}// 调整// NlogNcands.sort(new CandsComparator());daddy.sort(new DaddyComparator());// 是否要交换位置move(k, i);// 生成topK// Kans.add(getCurAns());}return ans;}// 将中奖区的topK放到 List<Integer> 中public List<Integer> getCurAns() {List<Integer> list = new ArrayList<>();for (Customer c : this.daddy) {list.add(c.id);}return list;}// 删除daddy/cands中购买数为0的顾客public 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 void move(int k, int time) {if (cands.isEmpty()) {return;}if (daddy.size() < k) {Customer c = cands.get(0);c.enterTime = time;cands.remove(0);daddy.add(c);} else if (cands.get(0).buy > daddy.get(0).buy) {Customer c1 = cands.get(0);c1.enterTime = time;Customer c2 = daddy.get(0);c2.enterTime = time;cands.remove(0);daddy.remove(0);daddy.add(c1);cands.add(c2);}}// 比较器// 中奖区的比较器 -> 最烂的在前面public 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 class CandsComparator implements Comparator<Customer> {@Overridepublic int compare(Customer o1, Customer o2) {return o1.buy != o2.buy ? (o2.buy - o1.buy) : (o1.enterTime - o2.enterTime);}}
}
大根堆解法:
package algorithmbasic.class8;import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;// 用加强堆实现
public class EveryStepShowBoss3 {// 顾客内部类public static class Customer {public int id;public int enterTime;public int buy;public Customer(int id, int enterTime, int buy) {this.id = id;this.enterTime = enterTime;this.buy = buy;}}// 比较器// 中奖区的比较器 -> 最烂的在前面public 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 class CandsComparator implements Comparator<Customer> {@Overridepublic int compare(Customer o1, Customer o2) {return o1.buy != o2.buy ? (o2.buy - o1.buy) : (o1.enterTime - o2.enterTime);}}// 属性// 查找是否有这个顾客 --> (根据id找customer)HashMap<Integer, Customer> map;// 得奖区HeapGreater<Customer> daddy;// 等候区HeapGreater<Customer> cands;// 中奖区最多容纳人数final int daddyLimit;// 构造器public EveryStepShowBoss3(int daddyLimit) {this.daddyLimit = daddyLimit;map = new HashMap<>();daddy = new HeapGreater<>(new DaddyComparator());cands = new HeapGreater<>(new CandsComparator());}// 方法public List<List<Integer>> topK(int[] arr, boolean[] op, int k) {// 返回的结果存放区List<List<Integer>> ans = new ArrayList<>();EveryStepShowBoss3 everyStepShowBoss3 = new EveryStepShowBoss3(k);for (int i = 0; i < arr.length; i++) {operate(i, arr[i] , op[i] , ans);}return ans;}public void operate(int time, int id, boolean buyOrRefund , List<List<Integer>> ans) {// 没有这个顾客并且退货if(!map.containsKey(id) && !buyOrRefund) {ans.add(this.getCurAns());return;}// 没有这个顾客 -> 买// 有这个顾客 -> 退// 有这个顾客 -> 买if(!map.containsKey(id)) {Customer c = new Customer(id, time, 1);map.put(id, c);if(daddy.size() < daddyLimit) {// 会自动调整daddy.push(c);}else {cands.push(c);}}else {Customer c = map.get(id);if(buyOrRefund) {c.buy++;}else {c.buy--;}if(cands.contains(c)) {if(c.buy == 0) {cands.remove(c);map.remove(id);}else {// 调整一下cands.resign(c);}}else {if(c.buy == 0) {daddy.remove(c);map.remove(id);}else {// 调整一下daddy.resign(c);}}}// 是否要交换位置move(time);// 生成topKans.add(this.getCurAns());}public void move(int time) {if(cands.isEmpty()) {return;}if(daddy.size() < daddyLimit) {Customer c = cands.pop();c.enterTime = time;daddy.push(c);}else {if(cands.peek().buy > daddy.peek().buy) {Customer c1 = cands.pop();c1.enterTime = time;Customer c2 = daddy.pop();c2.enterTime = time;cands.push(c2);daddy.push(c1);}}}// 将中奖区的topK放到 List<Integer> 中public List<Integer> getCurAns() {List<Customer> c = daddy.getAllElements();List<Integer> list = new ArrayList<>();for (Customer cus : c) {list.add(cus.id);}return list;}
}
Heapgreater
package algorithmbasic.class8;import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;// 加强堆
public class HeapGreater<T> {private ArrayList<T> heap;private int heapSize;private HashMap<T, Integer> indexMap;private Comparator<? super T> comp;public HeapGreater(Comparator<? super T> comp) {this.heap = new ArrayList<>();indexMap = new HashMap<>();this.heapSize = 0;this.comp = comp;}public boolean isEmpty() {return this.heapSize == 0;}public int size() {return this.heapSize;}public boolean contains(T obj) {return this.indexMap.containsKey(obj);}public T peek() {return this.heap.get(0);}// logNpublic void push(T obj) {heap.add(obj);indexMap.put(obj, heapSize);heapInsert(heapSize++);}// logNpublic void heapInsert(int index) {int father = (index - 1) / 2;while (this.comp.compare(heap.get(index), heap.get(father)) < 0) {// 注意交换的时候,ArrayList中的K V需要交换,HashMap中的K V也需要交换。swap(father, index);index = father;father = (index - 1) / 2;}}// logNpublic T pop() {T ans = heap.get(0);swap(0, --heapSize);indexMap.remove(ans);// 注意:其实Arraylist里面已经有一个heapsize这样的尾指针,这里我们自己定义的heapsize只是为了方便我们找到尾节点下标。// 真正意义上删除一个节点还是需要Arraylist里面的尾指针进行操作的。heap.remove(heapSize); // 真正的将尾节点删除。heapFiy(0);return ans;}// logNpublic void heapFiy(int index) {int l = index * 2 + 1;while (l < heapSize) {int minSonIndex = l + 1 < heapSize ? (this.comp.compare(heap.get(l), heap.get(l + 1)) < 0 ? l : l + 1) : (l);if(this.comp.compare(heap.get(minSonIndex), heap.get(index)) < 0) {swap(minSonIndex, index);index = minSonIndex;l = index * 2 + 1;}else {break;}}}// logNpublic void remove(T obj) {// 得到节点的位置indexint index = indexMap.get(obj);// 得到末尾的数 valueT replace = heap.get(--heapSize);// 将hashmap中obj值以及heap中末尾的数直接删掉indexMap.remove(obj);// 真正的删除尾节点。heap.remove(heapSize);// 如果末尾节点值与obj是同一个,不需要一下操作if (obj != replace) {// 将结果塞回去heap.set(index, replace);indexMap.put(replace, index);resign(replace);}}// 重构造// 只会执行其中的一个,logNpublic void resign(T obj) {heapInsert(indexMap.get(obj));heapFiy(indexMap.get(obj));}// 请返回堆上的所有元素public List<T> getAllElements() {List<T> ans = new ArrayList<>();for (T c : heap) {ans.add(c);}return ans;}public void swap(int i, int j) {T value1 = heap.get(i);T value2 = heap.get(j);heap.set(i, value2);heap.set(j, value1);// HashMap新的值覆盖旧的值indexMap.put(value1, j);indexMap.put(value2, i);}
}