• Heap简介


    概念

    堆是一种基于完全二叉树的数据结构,其中每个父节点都大于等于/小于等于其子节点。可以分为最大堆(Max Heap)和最小堆(Min Heap),其中最大堆要求父节点的值大于或等于所有子节点,而最小堆要求父节点的值小于或等于所有子节点。

    特点

    • 使用数组实现:通过利用数组索引表示完全二叉树中元素之间关系来节省内存空间。
    • 快速查找:插入、删除和获取操作具有对数时间复杂度。
    • 动态扩展:能够自动调整大小以容纳更多元素。

    优点

    1. 高效插入和删除操作:插入和删除具有对数时间复杂度。
    2. 高效获取最大/最小元素:可以快速获取具有极端值的顶部元素。

    缺点

    1. 不支持随机访问:堆不同于数组或链表,无法直接通过索引访问特定位置上的元素。
    2. 中间位置元素难以访问和更新:要查找、更新或删除非顶部位置上的元素会变得复杂且低效。

    适用场景

    • 需要快速获取最大/最小元素的场景。
    • 常用于优先队列、排序算法(如堆排序)等领域。

    示例代码

    下面是一个以最大堆为例的java代码简单实现:

    1. import java.util.Arrays;
    2. import java.util.NoSuchElementException;
    3. class Heap {
    4. private int[] heapArray;
    5. private int maxSize;
    6. private int currentSize;
    7. public Heap(int size) {// 构造函数,创建一个具有指定大小的堆对象。
    8. this.maxSize = size;
    9. this.currentSize = 0;
    10. this.heapArray = new int[size];
    11. }
    12. public boolean isEmpty() {// 检查堆是否为空,并返回布尔值。
    13. return currentSize == 0;
    14. }
    15. public boolean isFull() {// 检查堆是否已满,并返回布尔值。
    16. return currentSize == maxSize;
    17. }
    18. public void insert(int value) {// 向堆中插入一个元素。如果堆已满,则抛出异常。该方法使用“上溯”操作来维护堆性质。
    19. if (isFull()) {
    20. throw new IllegalStateException("Heap is full");
    21. }
    22. heapArray[currentSize] = value;
    23. trickleUp(currentSize);
    24. currentSize++;
    25. }
    26. private void trickleUp(int index) {//从给定索引开始执行上溯操作以维护堆性质。它将当前节点与其父节点进行比较和交换,直到达到合适的位置或达到根节点为止。
    27. int parentIndex = (index - 1) / 2; // 计算父节点索引
    28. while (index > 0 && heapArray[parentIndex] < heapArray[index]) {
    29. // 交换父节点与子节点的值
    30. int temp = heapArray[parentIndex];
    31. heapArray[parentIndex] = heapArray[index];
    32. heapArray[index] = temp;
    33. index = parentIndex;
    34. parentIndex = (index - 1) / 2; // 更新父节点索引
    35. }
    36. }
    37. public int remove() {// 移除并返回顶部(最大)元素。如果堆为空,则抛出异常。该方法使用“下溯”操作来维护堆性质。
    38. if (isEmpty()) {
    39. throw new IllegalStateException("Heap is empty");
    40. }
    41. int root = heapArray[0]; // 根节点的值为最大/最小元素
    42. currentSize--;
    43. heapArray[0] = heapArray[currentSize];
    44. trickleDown(0);
    45. return root;
    46. }
    47. private void trickleDown(int index) {// 从给定索引开始执行下溯操作以维护堆性质。它将当前节点与其较大子节点进行比较和交换,直到没有子节点或找到合适的位置为止。
    48. int largerChildIndex;
    49. while (index < currentSize / 2) { // 判断是否有子节点
    50. int leftChildIndex = 2 * index + 1;
    51. int rightChildIndex= leftChildIndex + 1;
    52. if (rightChildIndex < currentSize &&
    53. heapArray[leftChildIndex]
    54. largerChildIndex=rightChildIndex;
    55. } else{
    56. largerChildIndex=leftChildIndex ;
    57. }
    58. if(heapArray[index]>=heapArray[largerChildIndex]){
    59. break; // 当前位置已满足堆性质,不需要调整,退出循环
    60. }
    61. swap(index, largerChildIndex);
    62. index=largerChildIndex;//更新索引以继续下一轮比较
    63. }
    64. }
    65. private void swap(int index1,int index2){ //用于交换数组中两个索引位置上的元素。
    66. int temp=heapArray[index1];
    67. heapArray[index1]=heapArray[index2];
    68. heapArray[index2] = temp;
    69. }
    70. public int[] getHeapArray() {// 返回堆中所有元素
    71. return Arrays.copyOf(heapArray, currentSize);
    72. }
    73. public int peek() {// 查看但不删除顶部元素
    74. if (currentSize == 0) {
    75. throw new NoSuchElementException("Heap is empty.");
    76. }
    77. return heapArray[0];
    78. }
    79. }
    80. public class Main {
    81. public static void main(String[] args) {
    82. //创建一个最大堆
    83. Heap heap = new Heap(10);
    84. // 插入元素
    85. heap.insert(80);
    86. heap.insert(75);
    87. heap.insert(60);
    88. heap.insert(68);
    89. heap.insert(55);
    90. // 打印堆中所有元素
    91. System.out.println("Current elements in the heap: " + Arrays.toString(heap.getHeapArray()));
    92. // 获取并移除顶部(最大)元素
    93. int maxElement=heap.remove();
    94. System.out.println("Maximum element removed from the heap: "+maxElement);
    95. //打印删除顶部元 素后的堆
    96. System.out.println("Current elements in the heap after removal:" + Arrays.toString(heap.getHeapArray()) );
    97. //插入新元素
    98. heap.insert(100);
    99. //查看但不删除顶部元素
    100. maxElement=heap.peek();
    101. System.out.println("Maximum element removed from the heap: "+maxElement);
    102. //打印堆中所有元素
    103. System.out.println("Current elements in the heap: " + Arrays.toString(heap.getHeapArray()));
    104. }
    105. }

    常见问题

    1. 元素大小问题:在构建或插入时,需要确保父节点与子节点具有正确的大小关系。如果使用自定义对象作为堆中的元素,请实现正确的比较器来定义大小关系。
    2. 动态调整容量:如果底层数组满了,则需要进行动态扩展以容纳更多的元素。这可能涉及重新分配内存和复制数据,并带来一些性能开销。
    3. 并发问题:Java提供了线程安全的优先队列实现,如PriorityBlockingQueue。如果在多线程环境中使用,请确保选择适当的实现以避免并发问题。

    总结

    堆是一种基于完全二叉树的数据结构,通过父节点与子节点之间的比较来维护其特性。它支持快速插入、删除和获取最大/最小元素的操作,并用于优先队列和排序算法(如堆排序)等场景。处理大小关系、动态调整容量和并发问题时要注意正确性和性能。

  • 相关阅读:
    WebGL模型视图投影矩阵
    【Linux安全之iptables自定义链】
    分布式任务调度平台——XXL-JOB
    一个示例学习C语言到汇编层面
    【教程】Pycharm社区版中打开jupyter的方法
    Gson简介
    Python版股市情感分析源代码,提取投资者情绪,为决策提供参考
    Redis企业级问题及解决方案
    刘二大人 PyTorch深度学习实践 笔记 P6 逻辑斯蒂回归
    字节架构师: Kafka 的消费者客户端详解
  • 原文地址:https://blog.csdn.net/aidscooler/article/details/133763452