比较器

  1. 比较器的本质就是重载比较运算符
  2. 比较器可以很好的应用在特殊标准的排序上
  3. 比较器可以很好的应用在根据特殊标准排序的结构.上
  4. 写代码变得异常容易,还用于范型编程
  5. 比如制定出自定义对象的比较规则
  6. 任何比较器:
    1. compare方法里,遵循一个统一的规范:
      1. 返回负数的时候,认为第一个参数应该排在前面
      2. 返回正数的时候,认为第二个参数应该排在前面
      3. 返回0的时候,认为无所谓谁放前面
  7. 可以用在数组集合的排序上也可以用在有序表中
  8. 有序表中相同的key不会覆盖,而是直接忽略
  9. lambda表达式中只有一条返回语句时,可以直接写要返回的内容

image.png

  1. 有序表中相同的key直接忽略,key是否相同取决于比较器的比较规则(假如是比较对象的id,id一样就说明一样,会忽略;而实际上这两个不是同一个对象!)
  2. 可以用对象的内存地址来进行比较

image.png

  1. hashmap中的key为基本类型时会发生覆盖!

image.png

  1. package class06;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.Comparator;
  5. import java.util.TreeMap;
  6. public class Code01_Comparator {
  7. public static class Student {
  8. public String name;
  9. public int id;
  10. public int age;
  11. public Student(String name, int id, int age) {
  12. this.name = name;
  13. this.id = id;
  14. this.age = age;
  15. }
  16. }
  17. // 任何比较器:
  18. // compare方法里,遵循一个统一的规范:
  19. // 返回负数的时候,认为第一个参数应该排在前面
  20. // 返回正数的时候,认为第二个参数应该排在前面
  21. // 返回0的时候,认为无所谓谁放前面
  22. public static class IdShengAgeJiangOrder implements Comparator<Student> {
  23. // 根据id从小到大,但是如果id一样,按照年龄从大到小
  24. @Override
  25. public int compare(Student o1, Student o2) {
  26. return o1.id != o2.id ? (o1.id - o2.id) : (o2.age - o1.age);
  27. }
  28. }
  29. public static class IdAscendingComparator implements Comparator<Student> {
  30. // 返回负数的时候,第一个参数排在前面
  31. // 返回正数的时候,第二个参数排在前面
  32. // 返回0的时候,谁在前面无所谓
  33. @Override
  34. public int compare(Student o1, Student o2) {
  35. /*if(o1.id < o2.id) {
  36. return -1;
  37. } else if(o1.id > o2.id){
  38. return 1;
  39. } else {
  40. return 0;
  41. }*/
  42. return o1.id - o2.id;
  43. }
  44. }
  45. public static class IdDescendingComparator implements Comparator<Student> {
  46. @Override
  47. public int compare(Student o1, Student o2) {
  48. return o2.id - o1.id;
  49. }
  50. }
  51. // 先按照id排序,id小的,放前面;
  52. // id一样,age大的,前面;
  53. public static class IdInAgeDe implements Comparator<Student> {
  54. @Override
  55. public int compare(Student o1, Student o2) {
  56. return o1.id != o2.id ? o1.id - o2.id : (o2.age - o1.age);
  57. }
  58. }
  59. public static void printStudents(Student[] students) {
  60. for (Student student : students) {
  61. System.out.println("Name : " + student.name + ", Id : " + student.id + ", Age : " + student.age);
  62. }
  63. }
  64. public static void printArray(Integer[] arr) {
  65. if (arr == null) {
  66. return;
  67. }
  68. for (int i = 0; i < arr.length; i++) {
  69. System.out.print(arr[i] + " ");
  70. }
  71. System.out.println();
  72. }
  73. public static class MyComp implements Comparator<Integer> {
  74. @Override
  75. public int compare(Integer o1, Integer o2) {
  76. return o2 - o1;
  77. }
  78. }
  79. public static class AComp implements Comparator<Integer> {
  80. // 如果返回负数,认为第一个参数应该拍在前面
  81. // 如果返回正数,认为第二个参数应该拍在前面
  82. // 如果返回0,认为谁放前面都行
  83. @Override
  84. public int compare(Integer arg0, Integer arg1) {
  85. return arg1 - arg0;
  86. // return 0;
  87. }
  88. }
  89. public static void main(String[] args) {
  90. Integer[] arr = { 5, 4, 3, 2, 7, 9, 1, 0 };
  91. Arrays.sort(arr, new AComp());
  92. for (int i = 0; i < arr.length; i++) {
  93. System.out.println(arr[i]);
  94. }
  95. System.out.println("===========================");
  96. Student student1 = new Student("A", 4, 40);
  97. Student student2 = new Student("B", 4, 21);
  98. Student student3 = new Student("C", 3, 12);
  99. Student student4 = new Student("D", 3, 62);
  100. Student student5 = new Student("E", 3, 42);
  101. // D E C A B
  102. Student[] students = new Student[] { student1, student2, student3, student4, student5 };
  103. System.out.println("第一条打印");
  104. Arrays.sort(students, new IdShengAgeJiangOrder());
  105. for (int i = 0; i < students.length; i++) {
  106. Student s = students[i];
  107. System.out.println(s.name + "," + s.id + "," + s.age);
  108. }
  109. System.out.println("第二条打印");
  110. ArrayList<Student> studentList = new ArrayList<>();
  111. studentList.add(student1);
  112. studentList.add(student2);
  113. studentList.add(student3);
  114. studentList.add(student4);
  115. studentList.add(student5);
  116. // N * logN
  117. studentList.sort(new IdShengAgeJiangOrder());
  118. for (int i = 0; i < studentList.size(); i++) {
  119. Student s = studentList.get(i);
  120. System.out.println(s.name + "," + s.id + "," + s.age);
  121. }
  122. // N * logN
  123. System.out.println("第三条打印");
  124. student1 = new Student("A", 4, 40);
  125. student2 = new Student("B", 4, 21);
  126. student3 = new Student("C", 4, 12);
  127. student4 = new Student("D", 4, 62);
  128. student5 = new Student("E", 4, 42);
  129. TreeMap<Student, String> treeMap = new TreeMap<>((a, b) -> (a.id - b.id));
  130. treeMap.put(student1, "我是学生1,我的名字叫A");
  131. treeMap.put(student2, "我是学生2,我的名字叫B");
  132. treeMap.put(student3, "我是学生3,我的名字叫C");
  133. treeMap.put(student4, "我是学生4,我的名字叫D");
  134. treeMap.put(student5, "我是学生5,我的名字叫E");
  135. for (Student s : treeMap.keySet()) {
  136. System.out.println(s.name + "," + s.id + "," + s.age);
  137. }
  138. }
  139. }

完全二叉树

  1. 满的就是完全二叉树,不满也处在满的路上,而且是从左往右每一层依次变满的二叉树
  2. 空树是完全二叉树,只有根节点的也是完全二叉树,不能跳过左节点直接有右节点
  3. 表示完全二叉树:
    1. 经典的二叉树节点
    2. 从0 出发的一段连续的数组结构
      1. 任何i位置左孩子的位置在 2 * i + 1
      2. 任何i位置右孩子的位置在 2 * i + 2
      3. 任何i位置父节点的位置在 (i - 1)/ 2
      4. 再加一个范围表示已经存在的大小

image.png image.png

堆(优先队列)

  1. 堆是完全二叉树
  2. 大根堆:最大值为头部的值,每一棵子树都要满足这个(而不是整棵树中的最大值)
  3. 小根堆:最小值为头部的值,每一棵子树都要满足这个(而不是整棵树中的最大值)
  4. 任何一棵子树违规,那么他都不算堆了
  5. 不要左子树小于父节点,这是搜索二叉树,与堆无关
  6. 两个重要操作:heapinsert和heapify
  7. 系统中有现成的优先队列(PriorityQueue)===>默认是小根堆,基本类型可以不传比较器
  8. 中间有一个位置变化了,直接用heapinsert和heapify顺序调用,两个其实只发生一个(也可以互换顺序,两个只会发生一个,不发生的那个会进去检查,但是绝对不会出错)
  9. 堆结构中可以加重复值
  10. 复杂度:heapinsert和heapify都是O(logN)级别的(只与树的高度相关,最差沉到底是logN)

堆排序

  1. 给定一个无序数组,能不能调整成大根堆
    1. 整个数组中所有的数都调整成大根堆(假设一个一个给你)
    2. 将0位置的数与N-1位置的数做交换,最大值到N-1位置上了
    3. 将N-1位置断掉,size—
    4. 对新出现的换上来的顶部进行heapify
    5. 重复上述三个过程,直到size减为1(只剩一个数一定是排好序的)
  2. 复杂度为:O(N logN)(建堆是O(N logN),互换再调整也是O(N logN),加起来还是O(N logN)
  3. 还有一种从下往上建堆的方法是O(N)的,但是堆排序仍突破不了O(N logN),因为第二步(将大根堆的最顶上的元素换到最后在进行heapify)锁死了,不可能比O(N logN)之前更好
  4. 基于比较的排序中最好复杂度为O(N * logN),有不基于比较的排序

分析复杂度

  1. 定性分析
  2. 增加数据量时,整个过程的复杂度是不是原来的那个
    1. 数据量波动的时候,怎么算复杂度===>开始的时候没有达到那么高的高度却以那么高的高度进行计算
    2. 数据量增加常数法(N加倍即数据量加倍,复杂度是不会变化的)

image.png

  1. N logN是数据量为N时的上限,只要确定数据量为2N时的下限也是N logN即可
  2. N * logN为什么是上限?因为有的没有那么高也算那么高了,以最坏情况估计logN
  3. N * logN为什么是下限?因为2N时后一半的N一定有logN那么高了,以最好情况估计logN
  4. 夹逼===>数据量扩倍法(常用方法)


3. 从下往上建堆的方法===>O(N)

  1. 将之前无序的数组就看成是完全二叉树
  2. 将最底层的子树(叶结点)都用heapify调整成大根堆(向下沉调整成大根堆)
  3. 从下向上变成大根堆

从下向上构建堆.gif

  1. 具体实现上只要对数组从最后一个元素的位置进行heapify,遍历到位置0即可
  2. ❓最底层的数据最多,但是不用下沉,成本低了???

image.png image.png

  1. 实质:
    1. 上->下:一个节点承担一层,两个节点承担两层,四个节点承担三层……
      1. 大量节点是高度增加之后加上去的,高度越高承担的节点越多
    2. 下->上:大量节点是层数少的,而少量的节点才是层数高的
  2. 用户一下子把数组给出才可以用从下往上,假如用户一个一个给你,只能从上向下(没办法一股脑去做);而数组排序可以一下子把数组给你
  3. 堆排序中可以只含有heapify方法的,可以不要heapinsert
  4. 堆结构必须要有heapinsert
  5. 错位相减是计算技巧(分母一样,可以构成等比数列)===>幂级数收敛计算

附加题

  • 有一相对有序的数组(给出k,表示某一数与原来他应该所在的位置的的最大距离)
  • 构建一个小根堆(先将数组中前k+1个数放到堆中去,小根堆最大为k+1),可以用系统实现的PriorityQueue
  • 将小根堆中弹出的第一个数放到数组的0位置上去,一定是对的;并且将之前的后一个数也放到小根堆中去
  • 弹出一个数放到1位置,再后一个数放到堆中去
  • ………………(加一个弹一个)
  • 复杂度:O(NlogK) ===>比O(NlogN)好
  • 最后不够了,没法加了;只需要将栈弹空,把数组剩下的几个位置填满就完成了
  • 额外空间复杂度是O(K+1)
  • 解耦,先heapify,再heapinsert,不能将他们混合杂糅起来,那样太抽象了,写不出来

🤏随想

  1. 系统中的优先队列===>假如堆中一个元素的值发生了变化(甚至不知道是大了还是小了)===>从7位置开始看看能不能进行heapinsert(7),然后看看能不能进行heapify(7);两个方法顺序调用,调用完成之后,整个堆就变成了正常的样子===>两个必然只执行一个!!!顺序调用两个其实只发生一个,也可以先2后1
  2. 系统中的优先队列默认是小根堆,可以通过定义比较器将其定义为大根堆(将比较器传到优先队列中去)
  3. 成员变量定义时初始化和之后初始化(构造器或者代码块)的区别https://blog.csdn.net/weixin_43271086/article/details/91470725