通过JAVA实现堆(优先队列)的数据结构

一、什么是堆

我们日常生活中经常见到各种堆,比如冬天的雪堆,施工的土堆,都是下面大,上面尖顶的样子,那么数据结构中的堆与日常生活中的堆有哪些异同呢?

日常生活中的堆与数据结构中的堆

1.堆的性质
  • 堆是一个完全二叉树(除了最后一层,其他层从左到右的节点都是满的)
  • 堆中的节点,都大于等于或小于等于其子节点
  • 堆通常使用数组来存储
2.堆的分类
  • 大顶堆
    • 所有节点大于或等于其子节点

大顶堆

  • 小顶堆
    • 所有节点大于或小于其子节点

小顶堆

二、通过数组实现堆元素的存储

因为堆是一个完全二叉树

通过数组来存储堆元素

三、通过JAVA实现堆的数据结构

注:这里以小顶堆为例

1.定义堆结构:
  • size : 树节点个数
  • nodes : 节点值集合
  1. public class MyArrayHeap<T> {
  2. /**
  3. * 节点个数
  4. */
  5. private int size;
  6. /**
  7. * 节点数组
  8. */
  9. private T[] nodes;
  10. /**
  11. * 最小容量
  12. */
  13. private static final int MIN_CAPACITY = 8;
  14. }
2.添加节点
(1)添加节点的流程图

添加节点的流程图

  • 将新节点添加到树的末尾,即数组的末尾
  • 再将插入的节点与其父节点做对比,如果父节点大于插入的节点,则交换二者的位置,直到插入的节点比父节点小为止
(2)添加节点的实现
  1. /**
  2. * 添加节点
  3. * @param element
  4. * @return
  5. */
  6. public boolean add(T element) {
  7. //1.如果原数组是空数组,直接将元素插入进去,当作根节点
  8. if (size == 0) {
  9. nodes[0] = element;
  10. return true;
  11. }
  12. //2.如果数组长度达到节点的个数,则扩容
  13. if (size >= nodes.length ) {
  14. ensureCapacity(size * 2 + 1);
  15. }
  16. //3.定义插入位置的索引index,默认插入到树的最后
  17. int index = size;
  18. Comparable<T> node = (Comparable<T>) element;
  19. //4.循环比较插入节点与当前的父节点的大小,如果大于父节点,不用交换,否则,要交换位置
  20. while (index > 0) {
  21. //父节点的索引
  22. int parent = (index - 1) / 2;
  23. //父节点的元素值
  24. T e = nodes[parent];
  25. //如果当前插入节点(子节点)比父节点大,那么当前位置就是要插入的位置
  26. if (node.compareTo(e) >= 0) {
  27. break;
  28. }
  29. //插入节点位置与父节点位置交换
  30. nodes[index] = e;
  31. index = parent;
  32. }
  33. //最后得到插入的位置
  34. nodes[index] = element;
  35. return true;
  36. }
3.删除节点
(1)删除节点的流程图

删除节点的流程图

  • 删除根节点
  • 选出新的父节点
(2)删除节点的实现
  1. /**
  2. * 删除也叫删除最小元素
  3. * @return
  4. */
  5. public boolean remove() {
  6. if (size == 0) {
  7. return false;
  8. }
  9. //1.定义被最后替换的索引值
  10. int index = 0;
  11. //2.最后一个元素,也就是最后被移动的元素
  12. T element = nodes[size - 1];
  13. Comparable<T> node = (Comparable<T>) element;
  14. //父节点个数half
  15. int half = size / 2;
  16. while (index < half) {
  17. //子节点索引及子节点值
  18. int child = index * 2 + 1;
  19. T obj = nodes[child];
  20. //右子节点索引
  21. int rightChild = child + 1;
  22. //如果右子节点比左子节点小,那么右子节点移动到左子节点的位置
  23. if (rightChild < size && ((Comparable<T>) obj).compareTo(nodes[rightChild]) > 0) {
  24. child = rightChild;
  25. obj = nodes[child];
  26. }
  27. if (node.compareTo(obj) <= 0) {
  28. break;
  29. }
  30. nodes[index] = obj;
  31. index = child;
  32. }
  33. //最后一个元素仍然存储在数组最后一个位置上
  34. nodes[index] = element;
  35. size --;
  36. return true;
  37. }
4.完整实现
  1. package main.java.com.shixinke.java.demo.dataStruct;
  2. public class MyArrayHeap<T> {
  3. /**
  4. * 节点个数
  5. */
  6. private int size;
  7. /**
  8. * 节点数组
  9. */
  10. private T[] nodes;
  11. /**
  12. * 最小容量
  13. */
  14. private static final int MIN_CAPACITY = 8;
  15. public MyArrayHeap() {
  16. clearHeap(MIN_CAPACITY);
  17. }
  18. public MyArrayHeap(int capacity) {
  19. clearHeap(capacity);
  20. }
  21. public void clear() {
  22. clearHeap(MIN_CAPACITY);
  23. }
  24. /**
  25. * 获取节点个数
  26. * @return
  27. */
  28. public int size() {
  29. return size;
  30. }
  31. /**
  32. * 是否为空堆
  33. * @return
  34. */
  35. public boolean isEmpty() {
  36. return size == 0;
  37. }
  38. /**
  39. * 添加节点
  40. * @param element
  41. * @return
  42. */
  43. public boolean add(T element) {
  44. //1.如果原数组是空数组,直接将元素插入进去,当作根节点
  45. if (size == 0) {
  46. nodes[0] = element;
  47. size = 1;
  48. return true;
  49. }
  50. //2.如果数组长度达到节点的个数,则扩容
  51. if (size >= nodes.length ) {
  52. ensureCapacity(size * 2 + 1);
  53. }
  54. //3.定义插入位置的索引index,默认插入到树的最后
  55. int index = size;
  56. Comparable<T> node = (Comparable<T>) element;
  57. //4.循环比较插入节点与当前的父节点的大小,如果大于父节点,不用交换,否则,要交换位置
  58. while (index > 0) {
  59. //父节点的索引
  60. int parent = (index - 1) / 2;
  61. //父节点的元素值
  62. T e = nodes[parent];
  63. //如果当前插入节点(子节点)比父节点大,那么当前位置就是要插入的位置
  64. if (node.compareTo(e) >= 0) {
  65. break;
  66. }
  67. //插入节点位置与父节点位置交换
  68. nodes[index] = e;
  69. index = parent;
  70. }
  71. //最后得到插入的位置
  72. nodes[index] = element;
  73. size ++;
  74. return true;
  75. }
  76. /**
  77. * 删除也叫删除最小元素
  78. * @return
  79. */
  80. public boolean remove() {
  81. if (size == 0) {
  82. return false;
  83. }
  84. //1.定义被最后替换的索引值
  85. int index = 0;
  86. //2.最后一个元素,也就是最后被移动的元素
  87. T element = nodes[size - 1];
  88. Comparable<T> node = (Comparable<T>) element;
  89. //父节点个数half
  90. int half = size / 2;
  91. while (index < half) {
  92. //子节点索引及子节点值
  93. int child = index * 2 + 1;
  94. T obj = nodes[child];
  95. //右子节点索引
  96. int rightChild = child + 1;
  97. //如果右子节点比左子节点小,那么右子节点移动到左子节点的位置
  98. if (rightChild < size && ((Comparable<T>) obj).compareTo(nodes[rightChild]) > 0) {
  99. child = rightChild;
  100. obj = nodes[child];
  101. }
  102. if (node.compareTo(obj) <= 0) {
  103. break;
  104. }
  105. nodes[index] = obj;
  106. index = child;
  107. }
  108. //最后一个元素仍然存储在数组最后一个位置上
  109. nodes[index] = element;
  110. size --;
  111. return true;
  112. }
  113. private void clearHeap(int capacity) {
  114. size = 0;
  115. ensureCapacity(capacity);
  116. }
  117. /**
  118. * 扩容
  119. * @param capacity
  120. */
  121. private void ensureCapacity(int capacity) {
  122. if (capacity < size) {
  123. return;
  124. }
  125. T[] oldNodes = nodes;
  126. nodes = (T[]) new Object[capacity];
  127. for (int i = 0; i< size; i++) {
  128. nodes[i] = oldNodes[i];
  129. }
  130. }
  131. //打印节点
  132. public void print() {
  133. System.out.println();
  134. System.out.print("[");
  135. for (int i = 0; i < size; i++) {
  136. System.out.print(i+"="+nodes[i]);
  137. if (i < size -1) {
  138. System.out.print(",");
  139. }
  140. }
  141. System.out.print("]");
  142. System.out.println();
  143. }
  144. //演示测试 
  145. public static void main(String[] args) {
  146. MyArrayHeap<Integer> heap = new MyArrayHeap<Integer>();
  147. heap.add(21);
  148. heap.add(23);
  149. heap.add(25);
  150. heap.add(24);
  151. heap.add(26);
  152. heap.add(27);
  153. heap.add(29);
  154. heap.add(28);
  155. heap.add(30);
  156. //heap.print(); //[0=21,1=23,2=25,3=24,4=26,5=27,6=29,7=28,8=30]
  157. heap.remove();
  158. heap.print(); //[0=23,1=24,2=25,3=28,4=26,5=27,6=29,7=30]
  159. }
  160. }