• 能带你起飞的【数据结构】成王第十一篇:堆


    前言

     

    二叉树的顺序存储

    之前写的二叉树都是以链式存储的,左孩子,右孩子存地址,每个节点通过left和right串联起来.

    今天写顺序存储一棵二叉树 

    存储方式 

    使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中。

    一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。

    这种方式的主要用法就是堆的表示。

     下标关系 :

    已知双亲(parent)的下标,则:

    左孩子(left)下标 = 2 * parent + 1;

    右孩子(right)下标 = 2 * parent + 2;

    已知孩子(不区分左右)(child)下标,则: 双亲(parent)下标 = (child - 1) / 2;

    堆(heap) 

    1. 堆逻辑上是一棵完全二叉树

    2. 堆物理上是保存在数组中

    3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆

    4. 反之,则是小堆,或者小根堆,或者最小堆

    5.堆的基本作用是,快速找集合中的最值

    实现一个堆 

    堆分为大根堆和小根堆

    我们首先要把这棵二叉树调整为大根堆

    要变成大根堆,每棵子树也必须是大根堆

    首先比较左右孩子,左右孩子大的再和根节点比较大小,孩子比根大就互换,小就不变.

     只要变成大根堆一定能保证最上面的根节点是最大的元素

    大根堆不是说就是有序的

    总结:

    1.调整是从最后一棵子树出发的

    2.每棵子树的调整都是向下调整的(根和左右孩子去比较,根小根就下来了,这就叫做向下调整)

    问题:

    1.如何找到最后一棵子树?

    整棵树是一个数组,那么最后一个元素就是len - 1

    得到父亲节点parent = ((len-1)-1)/2

    2.parent--就可以遍历完每棵子树

    3.写一个向下调整的函数

    4.每棵树的调整结束位置,如何判定?

    每棵树调整的结束位置实际上都是一样的len

    1. public class TestHeap {
    2. public int elem[];
    3. public int usedSize;
    4. public TestHeap(){
    5. this.elem = new int[10];
    6. }
    7. /**
    8. * 向下调整函数的实现
    9. * @param parent 每棵树的根节点
    10. * @param len 每棵树的调整的结束位置
    11. */
    12. public void shiftDown(int parent ,int len){
    13. int child = 2 * parent + 1;
    14. //1.最起码是有左孩子的,至少是有一个孩子的
    15. while(child < len){
    16. if(child + 1 < len && elem[child] > elem[child + 1]){
    17. child++;//保证当前左右孩子最大值的下标
    18. }
    19. if(elem[child] > elem[parent]){
    20. int tmp = elem[child];
    21. elem[child] = elem[parent];
    22. elem[parent] = tmp;
    23. parent = child;
    24. child = 2 * parent + 1;
    25. }else{
    26. break;
    27. }
    28. }
    29. }
    30. //创建一个大根堆
    31. public void createBigHeap(int[] array ){
    32. for (int i = 0; i < array.length ; i++) {
    33. //将数据放到 elem 当中
    34. elem[i] = array[i];
    35. usedSize++;
    36. }
    37. //parent到 < 0 的时候就不能进行向下调整了
    38. for (int parent = (usedSize - 1 -1)/2; parent >= 0 ; parent--) {
    39. //调整
    40. shiftDown(parent,usedSize);
    41. }
    42. }
    43. }

     此时我们就有了一个堆

    大堆还是小堆需要验证:

     打印结果

     小堆 

    入队操作(向上调整):

     假设在没满的情况下,要放一个80到里面,如何放

     把80放到最后一个,此时80的下标就是10 

    那么此时是大根堆吗?不是,需要进行调整

    直接让80跟37比较(因为在插入80之前已经是大根堆了)

    交换

    这就是向上调整

    出队列(向下调整):

    每次出队列都得保证出最大的或者最小的 

    第一步:交换0下标和最后一个元素

    每次出都相当于usedSize都会减减 

    此时80就出出去了 

    此时我们就可以发现只有一棵树不是大根堆(0下标这棵树)

    第二步:调整0下标这棵树就可以了(进行向下调整)

    完整代码

    1. public class TestHeap {
    2. public int elem[];
    3. public int usedSize;
    4. public TestHeap(){
    5. this.elem = new int[10];
    6. }
    7. /**
    8. * 向下调整函数的实现
    9. * @param parent 每棵树的根节点
    10. * @param len 每棵树的调整的结束位置
    11. */
    12. public void shiftDown(int parent ,int len){
    13. int child = 2 * parent + 1;
    14. //1.最起码是有左孩子的,至少是有一个孩子的
    15. while(child < len){
    16. if(child + 1 < len && elem[child] > elem[child + 1]){
    17. child++;//保证当前左右孩子最大值的下标
    18. }
    19. if(elem[child] > elem[parent]){
    20. int tmp = elem[child];
    21. elem[child] = elem[parent];
    22. elem[parent] = tmp;
    23. parent = child;
    24. child = 2 * parent + 1;
    25. }else{
    26. break;
    27. }
    28. }
    29. }
    30. //创建一个大根堆
    31. public void createBigHeap(int[] array ){
    32. for (int i = 0; i < array.length ; i++) {
    33. //将数据放到 elem 当中
    34. elem[i] = array[i];
    35. usedSize++;
    36. }
    37. //parent到 < 0 的时候就不能进行向下调整了
    38. for (int parent = (usedSize - 1 -1)/2; parent >= 0 ; parent--) {
    39. //调整
    40. shiftDown(parent,usedSize);
    41. }
    42. }
    43. //向上调整
    44. private void shiftup(int child){
    45. int parent = (child - 1)/2;
    46. while(child > 0){
    47. if(elem[child] > elem[parent]){
    48. int tmp = elem[child];
    49. elem[child] = elem[parent];
    50. elem[parent] = tmp;
    51. child = parent;
    52. parent = (child - 1)/2;
    53. }
    54. }
    55. }
    56. //堆建好了往里面放元素
    57. public void offer( int val){
    58. if(isFull()){
    59. //扩容
    60. elem = Arrays.copyOf(elem,2*elem.length);
    61. }
    62. elem[usedSize++] = val;
    63. shiftup(usedSize-1);
    64. }
    65. //判满不满
    66. public boolean isFull(){
    67. return usedSize == elem.length;
    68. }
    69. //出队列
    70. public int poll(){
    71. if(isEmpty()){
    72. throw new RuntimeException("优先级队列为空");
    73. }
    74. int tmp = elem[0];
    75. elem[0] = elem[usedSize - 1];
    76. elem[usedSize - 1] = tmp;
    77. shiftDown(0,usedSize);
    78. return tmp;
    79. }
    80. public boolean isEmpty(){
    81. return usedSize == 0;
    82. }
    83. }

     

     

     

  • 相关阅读:
    【随记】在WSL2下安装ROS
    iOS面试:4.多线程GCD
    基于Flume+Kafka+Hbase+Flink+FineBI的实时综合案例(三)离线分析
    java实现数据库:mbdb-基于文件存储的关系型数据库
    为什么人们都讨厌开会?
    python基于django的同学校友录网站
    【华为机试真题详解】快速人名查找【2022 Q2 | 200分】
    《SpringMVC入门》SpringMVC工作原理
    聊聊基于Alink库的推荐系统
    SuperMap iServer 缓存直接发布及使用流程
  • 原文地址:https://blog.csdn.net/m0_64397675/article/details/125526846