• heap堆结构以及堆排序


    堆的定义

    堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

    • 堆中某个结点的值总是不大于或不小于其父结点的值;

    • 堆总是一棵完全二叉树。

    根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆斐波那契堆等。

    堆是非线性数据结构,相当于一维数组,有两个直接后继。

    堆使用数组保存

    使用一个一维数组保存堆数据

    堆顶位于index0位置,i位置的左孩子位于 2*i+1位置,右孩子位于2*i+2位置,父位置位于(i-1)/2位置

    堆操作

    pop:弹出堆顶元素并删除

    push:加入一个元素

    peek:查看堆顶元素

    heapify:从index位置,往下看,不断的下沉,直到到达或者堆最后位置

    heapInsert:新加进来的数,现在停在了index位置,依次往上移动直到适合位置结束

    code

    1. /**
    2. * title:Heap
    3. * description: 堆介绍及其相关操作
    4. * date: 2023/9/8
    5. * author: fooleryang
    6. * version: 1.0
    7. */
    8. /**
    9. * 堆 也叫优先级队列,是一颗 完全二叉树,其次,堆分为大根堆和小根堆
    10. * 完全二叉树
    11. *
    12. * 大根堆:
    13. * 子树的根节点是子树的最大值
    14. * 小根堆:
    15. * 子树的根节点是子数的最小值
    16. * 使用一个数组来表示一个堆
    17. *
    18. * 堆操作:
    19. * heapInsert:新加进来的数,现在停在了index位置,依次往上移动直到适合位置结束
    20. * heapify:从index位置,往下看,不断的下沉,直到到达或者堆最后位置
    21. * peek:查看堆顶元素
    22. * pop:弹出堆顶元素并删除
    23. * push:加入一个元素
    24. * 使用一个数组记录堆
    25. * 用heapSize控制堆大小,heapSize =0说明堆为空
    26. *
    27. */
    28. public class Heap {
    29. //大根堆
    30. //数组存放
    31. private int [] heap;
    32. //堆数据最大存储量
    33. private final int limit;
    34. //堆大小
    35. private int heapSize;
    36. public Heap(int limit){
    37. this.limit = limit;
    38. heap = new int[limit];
    39. heapSize = 0;
    40. }
    41. public boolean isEmpty(){
    42. return heapSize == 0;
    43. }
    44. public boolean isFull(){
    45. return heapSize == limit;
    46. }
    47. private void swap(int [] arr,int l,int r){
    48. int temp = arr[l];
    49. arr[l] = arr[r];
    50. arr[r] = temp;
    51. }
    52. /**
    53. * 功能:
    54. * 堆插入时调整
    55. * 即: 在arr数组中,插入元素的位置为index,现需要进行调整
    56. * 参数:
    57. * arr:待调整的堆
    58. * index:待调整位置
    59. * 步骤:
    60. * 如果当前index位置和父位置 (index-1)/2 比较
    61. * 大于则交换,继续比较,直到小于等于或者到达堆顶位置即0位置
    62. * 小于等于则停止
    63. * 关于 (index -1) /2
    64. * 完全二叉树的求父节点位置应该是 index/2
    65. * 但是这是建立在index从1开始时
    66. * 如果index从0开始,那么就是 (index -1) /2
    67. * 1,2,3,4,5,6,7 => index=7的父节点index => 7/2 = 3
    68. * 0,1,2,3,4,5,6 => index=5的父节点index => (6-1)/2 = 2
    69. */
    70. private void heapInsert(int [] arr,int index){
    71. //结束条件包含:
    72. //arr[index] == 父节点value
    73. //index 达到0位置 arr[0] arr[(0-1)/2 = 0]
    74. while (arr[index] > arr[(index -1) / 2]){
    75. swap(arr,index,(index-1)/2);
    76. index = (index -1) /2;
    77. }
    78. }
    79. /**
    80. * @Description: 从index开始节点下沉,直到index位置的孩子都比index小,或者已经没有孩子
    81. * @Param arr:堆
    82. * @Param index:开始下沉节点
    83. * @Param heapSize:堆大小
    84. * 关于孩子节点:
    85. * 完全二叉树,左孩子 2*i +1,右孩子 2*i+2
    86. */
    87. private void heapify(int [] arr,int index,int heapSize){
    88. //左孩子index
    89. int leftChildIndex = 2 * index + 1;
    90. //遍历条件:index还有孩子
    91. while (leftChildIndex < heapSize){
    92. //比较index的左右孩子,记录得到最大左右孩子的index
    93. //右孩子存在 并且 右孩子大于左孩子,返回右孩子index,否则返回左孩子index
    94. //右孩子不存在 返回左孩子index
    95. int largestIndex = leftChildIndex+11]?leftChildIndex+1:leftChildIndex;
    96. //如果孩子中最大的孩子小于等于index,则结束
    97. if(arr[largestIndex] <= arr[index])break;
    98. //交换
    99. swap(arr,largestIndex,index);
    100. //index节点下沉
    101. index = largestIndex;
    102. leftChildIndex = 2 * index +1;
    103. }
    104. }
    105. //新加入一个元素
    106. public void push(int value){
    107. //查看是否已经堆满
    108. if(heapSize == limit)
    109. throw new RuntimeException("heap is full");
    110. //将新加入的元素放到最后一个位置
    111. heap[heapSize] = value;
    112. //向上调整堆
    113. heapInsert(heap,heapSize);
    114. //堆增加
    115. heapSize++;
    116. }
    117. /**
    118. * @Description: 弹出堆顶元素
    119. * 思路:
    120. * 堆顶元素位置 0
    121. * 将堆顶元素与堆最后一个位置交换,堆长减一
    122. * 从新堆顶元素0进行下沉调整堆
    123. */
    124. public int pop(){
    125. int ans = heap[0];
    126. swap(heap,0,heapSize-1);
    127. heapSize--;
    128. heapify(heap,0,heapSize);
    129. return ans;
    130. }
    131. public int peek(){
    132. if(heapSize==0)return -1;
    133. return heap[0];
    134. }
    135. //test
    136. public static void main(String[] args) {
    137. Heap heap1 = new Heap(10);
    138. heap1.push(3);
    139. heap1.push(9);
    140. heap1.push(1);
    141. heap1.push(3);
    142. heap1.push(5);
    143. heap1.push(0);
    144. heap1.push(6);
    145. while (!heap1.isEmpty()){
    146. System.out.print(heap1.pop() + " ");
    147. }
    148. }
    149. }

    构建堆图示

    1. 已经构建好的堆

    2. 新加入元素

    3. 弹出堆顶元素

    堆排序

    时间复杂度 O(N*logN)

    思路

    待排序数组arr,将arr视作一个待排序的堆

    先将arr构建成一个大根堆

    再将大根堆arr调整为从小到大的顺序

    构建大根堆过程
    思路一

    遍历arr数组

    index = 0,arr 数组 0到index=0范围视为一个大根堆

    index = 1,视为在0-0的大根堆加入一个元素,进行heapInsert操作

    index = i,视为在0-(index-1)的大根堆加入元素

    直到数组全部加入

    1. for (int i = 0; i < arr.length; i++) {
    2. hearInsert(arr,i);
    3. }
    思路二

    arr数组视为一个待排序堆,叶子结点分别构成的子树只有一个元素,天然为一个大根堆

    依次往上构建的堆,则是需要调整,即新子树除了根节点其余子树都是大根堆,需要调整新子树的根节点,进行下沉调整

    直到所有子树构建成一颗子树

    如下图过程

    1. 叶子结点分别为一个大根堆

    2. 加入上层节点,需要进行下沉调整

    3. 下沉调整

    4. 继续步骤二 ,直到所有节点进入堆中

    将大根堆数组调整到大小顺序
    思路

    将堆顶元素和最后一个元素交换,此时最大元素到达数组最后位置

    将最后一个元素剔除堆

    此时堆顶元素为待调整的元素,进行下沉调整

    反复直到堆元素为空

    1. int heapSize = arr.length;
    2. do{
    3. swap(arr,0,--heapSize);// 等价与 swap(arr,0,heapSize-1);heapSize--;
    4. heapify(arr,0,heapSize);
    5. }while (heapSize > 0);
    code
    1. public static void heapSort(int [] arr){
    2. if(arr == null || arr.length < 2)return;
    3. //构建大根堆
    4. //从index = 0 开始
    5. //index = 0 ,则0-0为一个大根堆
    6. //inedx = 1,则将index=1插入到0-0堆中
    7. //index = i,则将index=i插入到0 到 (i-1)的堆中
    8. // O(N*logN)
    9. // for (int i = 0; i < arr.length; i++) {
    10. // hearInsert(arr,i);
    11. // }
    12. //或者这样调整,该步骤时间复杂度降低到O(N)
    13. //给定的数组是一个堆,只是顺序不对
    14. //从最低层即叶子节点开始下沉调整
    15. //最开始,叶子节点构成的子树为大根堆
    16. //上一层时,叶子节点构成的子树新加入一个节点并且位于子数的根节点,需要进行下沉调整
    17. //依次进行
    18. //O(N)
    19. for (int i = arr.length-1;i >=0;i--){
    20. heapify(arr,i,arr.length);
    21. }
    22. //将大根堆数组排序
    23. //将堆顶元素与数组最后一个元素交换
    24. //堆大小减一
    25. //交换完成后将堆顶元素下沉调整堆
    26. // int heapSize = arr.length;
    27. // swap(arr,0,heapSize-1);
    28. // heapSize--;
    29. // while (heapSize >0){
    30. // //调整
    31. // heapify(arr,0,heapSize);
    32. // swap(arr,0,heapSize-1);
    33. // heapSize--;
    34. // }
    35. int heapSize = arr.length;
    36. do{
    37. swap(arr,0,--heapSize);// 等价与 swap(arr,0,heapSize-1);heapSize--;
    38. heapify(arr,0,heapSize);
    39. }while (heapSize > 0);
    40. }
    41. private static void swap(int [] arr,int l,int r){
    42. int temp = arr[l];
    43. arr[l] = arr[r];
    44. arr[r] = temp;
    45. }
    46. //向下调整
    47. public static void heapify(int [] arr,int index,int heapSize){
    48. int left = index * 2 + 1;
    49. while (left < heapSize){
    50. int largest = left+1 < heapSize && arr[left+1] > arr[left]?left+1:left;
    51. if(arr[largest] < arr[index])break;
    52. swap(arr,index,largest);
    53. index = largest;
    54. left = index * 2 + 1;
    55. }
    56. }
    57. //插入一个新元素,向上调整
    58. public static void hearInsert(int [] arr,int index){
    59. int fatherIndex = (index - 1) / 2;
    60. while (arr[index] > arr[fatherIndex]){
    61. swap(arr,index,fatherIndex);
    62. index = fatherIndex;
    63. fatherIndex = (index - 1) / 2;
    64. }
    65. }
  • 相关阅读:
    解读 --- Span<T>
    训练与推理
    23软考备考已开始,网络工程师知识点速记~(4)
    java游戏制作-飞翔的鸟游戏
    centos7 环境安装 PM2 管理 node
    《乔布斯传》英文原著重点词汇笔记(八)【 chapter six 】
    docker&docker-copose_限制容器cpu和内存
    扫雷(C语言)
    乓乓响再度冲刺港股:来自临时及应急服务客户毛利率达70%
    云桌面 node_modules 切换艰辛历程记录 rebuild失败记录
  • 原文地址:https://blog.csdn.net/hey_lie/article/details/132779798