最大堆和最小堆-java代码实现
堆:
-
堆中某个结点的值总是不大于或不小于其父结点的值;
-
堆总是一棵完全二叉树。
将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。
1、依赖准备
堆底层数据存储结构为动态数组。
1 package com.wangymd.heap; 2 3 /** 4 * @desc 动态数组 5 * @author wangymd 6 * @data 2022-06-29 21:39:34 7 */ 8 @SuppressWarnings("unchecked") 9 public class Array<T> { 10 11 private T[] data; 12 13 private int size; 14 15 /** 16 * 无参构造方法 17 */ 18 public Array() { 19 this(10); 20 } 21 22 /** 23 * 指定容量 24 * @param capacity 25 */ 26 public Array(int capacity) { 27 super(); 28 data = (T[])new Object[capacity]; 29 this.size = 0; 30 } 31 32 public Array(T[] arr) { 33 data = (T[])new Object[arr.length]; 34 for (int i = 0; i < arr.length; i++) { 35 data[i] = arr[i]; 36 } 37 this.size = data.length; 38 } 39 40 /** 41 * 获取数组元素个数 42 * @return 43 */ 44 public int getSize() { 45 return size; 46 } 47 48 /** 49 * 获取数组容量 50 * @return 51 */ 52 public int getCapacity() { 53 return data.length; 54 } 55 56 /** 57 * 数组元素是否为空 58 * @return 59 */ 60 public Boolean isEmpty() { 61 return size == 0; 62 } 63 64 /** 65 * 指定索引位置插入元素 66 * @param index 67 * @param e 68 */ 69 public void add(int index, T e) { 70 if(index < 0 || index > size) { 71 throw new IllegalArgumentException("add failed, index < 0 || index > size"); 72 } 73 74 if(size == data.length) { 75 reSize(size * 2); 76 } 77 78 for(int i = size; i >= index; i--) { 79 data[size] = data[i]; 80 } 81 data[index] = e; 82 size++; 83 } 84 85 /** 86 * 扩容或缩容量 87 * @param newCapacity 88 */ 89 private void reSize(int newCapacity) { 90 T[] newData = (T[])new Object[newCapacity]; 91 for (int i = 0; i < size; i++) { 92 newData[i] = data[i]; 93 } 94 data = newData; 95 } 96 97 /** 98 * 尾部添加元素 99 * @param e 100 */ 101 public void addLast(T e) { 102 add(size, e); 103 } 104 105 /** 106 * 头部添加元素 107 * @param e 108 */ 109 public void addFirst(T e) { 110 add(0, e); 111 } 112 113 /** 114 * 获取指定索引位置元素 115 * @param index 116 * @return 117 */ 118 public T get(int index) { 119 if(index < 0 || index >= size) { 120 throw new IllegalArgumentException("get failed, index < 0 || index >= size"); 121 } 122 123 return data[index]; 124 } 125 126 /** 127 * 设置指定索引位置元素 128 * @param index 129 * @return 130 */ 131 public void set(int index, T e) { 132 if(index < 0 || index >= size) { 133 throw new IllegalArgumentException("set failed, index < 0 || index >= size"); 134 } 135 136 data[index] = e; 137 } 138 139 /** 140 * 判断元素是否存在 141 * @param e 142 * @return 143 */ 144 public Boolean contains(T e) { 145 for (int i = 0; i < size; i++) { 146 if(data[i].equals(e)) { 147 return true; 148 } 149 } 150 151 return false; 152 } 153 154 /** 155 * 查询元素是位置,不存在返回-1 156 * @param e 157 * @return 158 */ 159 public int find(T e) { 160 for (int i = 0; i < size; i++) { 161 if(e.equals(data[i])) return i; 162 } 163 164 return -1; 165 } 166 167 /** 168 * 删除指定索引位置元素,不存在返回-1 169 * @param e 170 * @return 171 */ 172 public T remove(int index) { 173 if(index < 0 || index >= size) { 174 throw new IllegalArgumentException("set failed, index < 0 || index >= size"); 175 } 176 177 T e = data[index]; 178 179 for (int i = index; i < size - 1; i++) { 180 data[i] = data[i + 1];//元素位置前移 181 } 182 183 size--; 184 data[size] = null;//释放空间 185 186 if(size == data.length / 4 && data.length / 2 != 0) { 187 reSize(data.length / 2); 188 } 189 190 return e; 191 } 192 193 /** 194 * 删除头部位置元素,不存在返回-1 195 * @param e 196 * @return 197 */ 198 public T removeFirst() { 199 return remove(0); 200 } 201 202 /** 203 * 删除头部位置元素,不存在返回-1 204 * @param e 205 * @return 206 */ 207 public T removeLast() { 208 return remove(size - 1); 209 } 210 211 /** 212 * 判断元素是否存在,如果存在删除元素 213 * @param e 214 * @return 215 */ 216 public void removeElement(T e) { 217 int index = find(e); 218 remove(index); 219 } 220 221 @Override 222 public String toString() { 223 StringBuilder stringBuilder = new StringBuilder(); 224 stringBuilder.append(String.format("Array size = %d, capacity = %d\n", size, data.length)); 225 stringBuilder.append("["); 226 for (int i = 0; i < size; i++) { 227 stringBuilder.append(data[i]); 228 if(i != size - 1) { 229 stringBuilder.append(", "); 230 } 231 } 232 stringBuilder.append("]"); 233 return stringBuilder.toString(); 234 } 235 }
2、最大堆
1 package com.wangymd.heap; 2 3 import java.util.Objects; 4 5 /** 6 * @desc 最大堆 7 * @author wangymd 8 * @data 2022-06-29 21:40:02 9 */ 10 public class MaxHeap<E extends Comparable<E>> { 11 12 private Array<E> datas; 13 14 public MaxHeap() { 15 this.datas = new Array<E>(); 16 } 17 18 public MaxHeap(E[] arr) { 19 this.datas = new Array<E>(arr); 20 21 // 最大非叶子节点索引 22 int parentIndex = parentIndex(datas.getSize() - 1); 23 24 // 非叶子节点节点下沉 25 for (int i = parentIndex; i >= 0; i--) { 26 siftDown(i); 27 } 28 } 29 30 /** 31 * 获取数组元素个数 32 * @return 33 */ 34 public int getSize() { 35 return datas.getSize(); 36 } 37 38 /** 39 * 数组元素是否为空 40 * @return 41 */ 42 public Boolean isEmpty() { 43 return datas.isEmpty(); 44 } 45 46 /** 47 * 计算父节点,i为坐标 1、父节点 (i)/2 2、左子节点 i * 2 3、右子节点 (i * 2) + 1 48 * @param index 49 * @return 50 */ 51 private int parentIndex(int index) { 52 if (index == 0) 53 throw new IllegalArgumentException("index 0 has not parent!"); 54 return (index - 1) / 2; 55 } 56 57 /** 58 * 计算左子节点 59 * @param index 60 * @return 61 */ 62 private int leftChildIndex(int index) { 63 return index * 2 + 1; 64 } 65 66 /** 67 * 计算右子节点 68 * @param index 69 * @return 70 */ 71 private int rightChildIndex(int index) { 72 return leftChildIndex(index) + 1; 73 } 74 75 /** 76 * 添加元素 77 * @param e 78 */ 79 public void add(E e) { 80 datas.addLast(e); 81 siftUp(getSize() - 1); 82 } 83 84 85 86 /** 87 * 删除最大元素 88 * @param e 89 */ 90 public E deleteMax() { 91 // 将第一个元素和最后元素互换 92 swap(0, getSize() - 1); 93 //System.out.println("删除前:" + this.toString()); 94 E e = datas.removeLast(); 95 siftDown(0); 96 //System.out.println("删除后:" + this.toString()); 97 98 return e; 99 } 100 101 /** 102 * 节点堆上浮调整 103 * @param index 104 */ 105 private void siftUp(int index) { 106 // 与父节点对比 107 while (index > 0 && datas.get(index).compareTo(datas.get(parentIndex(index))) > 0) { 108 int parentIndex = parentIndex(index); 109 //子节点和父节点交换 110 swap(index, parentIndex); 111 //索引执行父节点 112 index = parentIndex; 113 } 114 } 115 116 /** 117 * 堆下沉调整 118 * @param index 119 */ 120 private void siftDown(int index) { 121 int size = getSize(); 122 123 // 与子节点对比 124 //(leftChildIndex(index) < size)条件很重要,考虑不是完全二叉树的情况 125 while (leftChildIndex(index) < size) { 126 // 比较左右子节点取最大值 127 int leftChildIndex = leftChildIndex(index); 128 int rightChildIndex = rightChildIndex(index); 129 int maxChildIndex = leftChildIndex; 130 131 // 有可能没有右节点 132 if (rightChildIndex < size) { 133 maxChildIndex = datas.get(leftChildIndex).compareTo(datas.get(rightChildIndex)) > 0 ? leftChildIndex 134 : rightChildIndex; 135 } 136 137 // 父节点大于左右子节点 138 if (datas.get(index).compareTo(datas.get(maxChildIndex)) >= 0) { 139 break; 140 } 141 142 swap(index, maxChildIndex); 143 index = maxChildIndex; 144 } 145 } 146 147 /** 148 * 看堆中的最大元素 149 * @return 150 */ 151 public E findMax() { 152 if (datas.getSize() == 0) 153 throw new IllegalArgumentException("Can not findMax when heap is empty."); 154 return datas.get(0); 155 } 156 157 // 取出堆中的最大元素,并且替换成元素e 158 public E replace(E e) { 159 E max = findMax(); 160 161 datas.set(0, e); 162 siftDown(0); 163 164 return max; 165 } 166 167 /** 168 * 将任意数组整理成堆 169 * @param e 170 */ 171 public void heapify(E[] arr) { 172 if (arr == null || arr.length <= 0) 173 throw new IllegalArgumentException("Can not heapify when arr is empty."); 174 for (int i = 0; i < arr.length; i++) { 175 add(arr[i]); 176 } 177 } 178 179 /** 180 * 交换元素 181 * @param i1 182 * @param i2 183 */ 184 private void swap(int i1, int i2) { 185 E e = datas.get(i1); 186 datas.set(i1, datas.get(i2)); 187 datas.set(i2, e); 188 } 189 190 /** 191 * 判断是否包含元素 192 * @param e 193 */ 194 public boolean contains(E e) { 195 return findItem(e, 0) > 0; 196 } 197 198 /** 199 * 200 * @param e 201 * @param index 202 * @return 203 */ 204 private int findItem(E e, int index) { 205 if(index >= getSize()) { 206 return -1; 207 } 208 209 if(Objects.equals(e, datas.get(index))) { 210 System.out.println("findItem:" + index); 211 return index; 212 } 213 //TODO 左子节点 右子节点 查询 214 int findLeft = findItem(e, leftChildIndex(index)); 215 if(findLeft != -1) { 216 return leftChildIndex(index); 217 } 218 int findRight = findItem(e, rightChildIndex(index)); 219 if(findRight != -1) { 220 return rightChildIndex(index); 221 } 222 return -1; 223 } 224 225 @Override 226 public String toString() { 227 return datas.toString(); 228 } 229 }
测试1:
1 package com.wangymd.heap.test; 2 3 import com.wangymd.heap.MaxHeap; 4 5 /** 6 * @desc 测试最大堆 7 * @author wangymd 8 * @data 2022-06-29 21:40:17 9 */ 10 public class MaxHeapTest0 { 11 12 public static void main(String[] args) { 13 Integer[] nums = { 1, 4, 2, 5, 8, 9, 6 }; 14 // Integer[] nums = { 1, 4, 2 }; 15 16 MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(); 17 for (Integer e : nums) { 18 maxHeap.add(e); 19 } 20 21 System.out.println(maxHeap.contains(4)); 22 System.out.println(maxHeap.contains(10)); 23 24 int size = maxHeap.getSize(); 25 for (int i = 0; i < size; i++) { 26 System.out.println(maxHeap.deleteMax()); 27 } 28 } 29 30 }
测试2:
1 package com.wangymd.heap.test; 2 3 import java.util.Random; 4 5 import com.wangymd.heap.MaxHeap; 6 7 /** 8 * @desc 堆heapify测试复杂度 9 * @author wangymd 10 * @data 2022-06-29 21:39:50 11 */ 12 public class HeapifyTest { 13 14 private static Long test(Integer[] arr, boolean isHeapify) { 15 long start = System.currentTimeMillis(); 16 17 MaxHeap<Integer> maxHeap = null; 18 if(isHeapify) { 19 maxHeap = new MaxHeap<Integer>(arr); 20 }else { 21 maxHeap = new MaxHeap<Integer>(); 22 for (Integer e : arr) { 23 maxHeap.add(e); 24 } 25 } 26 27 int count = arr.length; 28 int[] array = new int[count]; 29 for (int i = 0; i < count; i++) { 30 array[i] = maxHeap.deleteMax(); 31 } 32 33 for (int i = 1; i < count; i++) { 34 if(array[i - 1] < array[i]) { 35 throw new IllegalArgumentException("error"); 36 } 37 } 38 39 long end = System.currentTimeMillis(); 40 return end - start; 41 } 42 43 public static void main(String[] args) { 44 int count = 10000000; 45 Random random = new Random(); 46 Integer[] array = new Integer[count]; 47 for (int i = 0; i < count; i++) { 48 array[i] = random.nextInt(Integer.MAX_VALUE); 49 } 50 // isHeapify=false耗时:14743 51 // isHeapify=true耗时:11947 52 System.out.println("isHeapify=false耗时:" + test(array, false)); 53 System.out.println("isHeapify=true耗时:" + test(array, true)); 54 } 55 56 }
3、最小堆
1 package com.wangymd.heap; 2 3 import java.util.Objects; 4 5 /** 6 * @desc 最小堆 7 * @author wangymd 8 * @data 2022-07-03 14:29:10 9 */ 10 public class MinHeap<E extends Comparable<E>> { 11 12 private Array<E> datas; 13 14 public MinHeap() { 15 this.datas = new Array<E>(); 16 } 17 18 public MinHeap(E[] arr) { 19 this.datas = new Array<E>(arr); 20 21 // 最大非叶子节点索引 22 int parentIndex = parentIndex(datas.getSize() - 1); 23 // 非叶子节点节点下沉 24 for (int i = parentIndex; i >= 0; i--) { 25 siftDown(i); 26 } 27 } 28 29 /** 30 * 获取数组元素个数 31 * @return 32 */ 33 public int getSize() { 34 return datas.getSize(); 35 } 36 37 /** 38 * 数组元素是否为空 39 * @return 40 */ 41 public Boolean isEmpty() { 42 return datas.isEmpty(); 43 } 44 45 /** 46 * 计算父节点,i为坐标 1、父节点 (i)/2 2、左子节点 i * 2 + 1 47 * @param index 48 * @return 49 */ 50 private int parentIndex(int index) { 51 if (index == 0) 52 throw new IllegalArgumentException("index 0 has not parent!"); 53 return (index - 1) / 2; 54 } 55 56 /** 57 * 计算左子节点 58 * @param index 59 * @return 60 */ 61 private int leftChildIndex(int index) { 62 return index * 2 + 1; 63 } 64 65 /** 66 * 计算右子节点 67 * @param index 68 * @return 69 */ 70 private int rightChildIndex(int index) { 71 return leftChildIndex(index) + 1; 72 } 73 74 /** 75 * 添加元素 76 * @param e 77 */ 78 public void add(E e) { 79 datas.addLast(e); 80 siftUp(getSize() - 1); 81 } 82 83 84 85 /** 86 * 删除最大元素 87 * @param e 88 */ 89 public E deleteMax() { 90 // 将第一个元素和最后元素互换 91 swap(0, getSize() - 1); 92 //System.out.println("删除前:" + this.toString()); 93 E e = datas.removeLast(); 94 siftDown(0); 95 //System.out.println("删除后:" + this.toString()); 96 97 return e; 98 } 99 100 /** 101 * 节点堆上浮调整 102 * @param index 103 */ 104 private void siftUp(int index) { 105 // 与父节点对比 106 while (index > 0 && datas.get(index).compareTo(datas.get(parentIndex(index))) < 0) { 107 int parentIndex = parentIndex(index); 108 //子节点和父节点交换 109 swap(index, parentIndex); 110 //索引执行父节点 111 index = parentIndex; 112 } 113 } 114 115 /** 116 * 堆下沉调整 117 * @param index 118 */ 119 private void siftDown(int index) { 120 int size = getSize(); 121 122 // 与子节点对比 123 //(leftChildIndex(index) < size)条件很重要,考虑不是完全二叉树的情况 124 while (leftChildIndex(index) < size) { 125 // 比较左右子节点取最小值 126 int leftChildIndex = leftChildIndex(index); 127 int rightChildIndex = rightChildIndex(index); 128 int mixChildIndex = leftChildIndex; 129 130 // 有可能没有右节点 131 if (rightChildIndex < size) { 132 mixChildIndex = datas.get(leftChildIndex).compareTo(datas.get(rightChildIndex)) < 0 ? leftChildIndex : rightChildIndex; 133 } 134 135 // 父节点小于左右子节点 136 if (datas.get(index).compareTo(datas.get(mixChildIndex)) <= 0) { 137 break; 138 } 139 140 swap(index, mixChildIndex); 141 index = mixChildIndex; 142 } 143 } 144 145 /** 146 * 看堆中的最大元素 147 * @return 148 */ 149 public E findMin() { 150 if (datas.getSize() == 0) throw new IllegalArgumentException("Can not findMax when heap is empty."); 151 return datas.get(0); 152 } 153 154 // 取出堆中的最小元素,并且替换成元素e 155 public E replace(E e) { 156 E min = findMin(); 157 158 datas.set(0, e); 159 siftDown(0); 160 161 return min; 162 } 163 164 /** 165 * 将任意数组整理成堆 166 * @param e 167 */ 168 public void heapify(E[] arr) { 169 if (arr == null || arr.length <= 0) 170 throw new IllegalArgumentException("Can not heapify when arr is empty."); 171 for (int i = 0; i < arr.length; i++) { 172 add(arr[i]); 173 } 174 } 175 176 /** 177 * 交换元素 178 * @param i1 179 * @param i2 180 */ 181 private void swap(int i1, int i2) { 182 E e = datas.get(i1); 183 datas.set(i1, datas.get(i2)); 184 datas.set(i2, e); 185 } 186 187 /** 188 * 判断是否包含元素 189 * @param e 190 */ 191 public boolean contains(E e) { 192 return findItem(e, 0) > 0; 193 } 194 195 /** 196 * 197 * @param e 198 * @param index 199 * @return 200 */ 201 private int findItem(E e, int index) { 202 if(index >= getSize()) { 203 return -1; 204 } 205 206 if(Objects.equals(e, datas.get(index))) { 207 System.out.println("findItem:" + index); 208 return index; 209 } 210 //TODO 左子节点 右子节点 查询 211 int findLeft = findItem(e, leftChildIndex(index)); 212 if(findLeft != -1) { 213 return leftChildIndex(index); 214 } 215 int findRight = findItem(e, rightChildIndex(index)); 216 if(findRight != -1) { 217 return rightChildIndex(index); 218 } 219 return -1; 220 } 221 222 @Override 223 public String toString() { 224 return datas.toString(); 225 } 226 }
测试1:
1 package com.wangymd.heap.test; 2 3 import java.util.Random; 4 5 import com.wangymd.heap.MinHeap; 6 7 /** 8 * @desc 堆heapify测试复杂度 9 * @author wangymd 10 * @data 2022-06-29 21:39:50 11 */ 12 public class HeapifyTest2MinHeap { 13 14 private static Long test(Integer[] arr, boolean isHeapify) { 15 long start = System.currentTimeMillis(); 16 17 MinHeap<Integer> minHeap = null; 18 if(isHeapify) { 19 minHeap = new MinHeap<Integer>(arr); 20 }else { 21 minHeap = new MinHeap<Integer>(); 22 for (Integer e : arr) { 23 minHeap.add(e); 24 } 25 } 26 27 int count = arr.length; 28 int[] array = new int[count]; 29 for (int i = 0; i < count; i++) { 30 array[i] = minHeap.deleteMax(); 31 } 32 33 for (int i = 1; i < count; i++) { 34 if(array[i - 1] > array[i]) throw new IllegalArgumentException("error"); 35 } 36 37 long end = System.currentTimeMillis(); 38 return end - start; 39 } 40 41 public static void main(String[] args) { 42 int count = 10000000; 43 Random random = new Random(); 44 Integer[] array = new Integer[count]; 45 for (int i = 0; i < count; i++) { 46 array[i] = random.nextInt(Integer.MAX_VALUE); 47 } 48 // isHeapify=false耗时:17395 49 // isHeapify=true耗时:19024 50 System.out.println("isHeapify=false耗时:" + test(array, false)); 51 System.out.println("isHeapify=true耗时:" + test(array, true)); 52 } 53 54 }
4、应用
优先队列应用:
1 package com.wangymd.heap; 2 3 4 /** 5 * @desc 队列 6 * @author wangymd 7 * @data 2022-06-29 21:40:41 8 */ 9 public interface Queue<T>{ 10 11 /** 12 * 获取队列元素数量 13 * @return 14 */ 15 int getSize(); 16 17 /* 18 * 判断队列是否为空 19 */ 20 Boolean isEmpty(); 21 22 /** 23 * 入队列 24 * @param t 25 */ 26 void enQueue(T t); 27 28 /** 29 * 出队列 30 * @return 31 */ 32 T deQueue(); 33 34 /** 35 * 获取队列首个元素 36 * @return 37 */ 38 T getQueue(); 39 }
实现:
1 package com.wangymd.heap; 2 3 4 /** 5 * @desc 优先队列 6 * @author wangymd 7 * @data 2022-06-29 21:40:28 8 */ 9 public class PriorityQueue<E extends Comparable<E>> implements Queue<E>{ 10 11 private MaxHeap<E> datas; 12 13 public PriorityQueue() { 14 datas = new MaxHeap<E>(); 15 } 16 17 @Override 18 public int getSize() { 19 return datas.getSize(); 20 } 21 22 @Override 23 public Boolean isEmpty() { 24 return datas.isEmpty(); 25 } 26 27 @Override 28 public void enQueue(E e) { 29 datas.add(e); 30 } 31 32 @Override 33 public E deQueue() { 34 return datas.deleteMax(); 35 } 36 37 @Override 38 public E getQueue() { 39 return datas.findMax(); 40 } 41 42 }
测试:
1 package com.wangymd.heap.test; 2 3 import java.util.LinkedList; 4 import java.util.List; 5 import java.util.Map; 6 import java.util.TreeMap; 7 8 import com.wangymd.heap.PriorityQueue; 9 10 /** 11 * @desc 前K个高频元素 12 * @author wangymd 13 * @data 2022-06-29 21:07:31 14 */ 15 public class PriorityQueueTest { 16 17 private class Freq<E> implements Comparable<Freq<E>> { 18 E e; 19 int freq; 20 21 public Freq(E e, int freq) { 22 super(); 23 this.e = e; 24 this.freq = freq; 25 } 26 27 @Override 28 public int compareTo(Freq<E> o) { 29 if (this.freq < o.freq) 30 return 1; 31 else if (this.freq == o.freq) 32 return 0; 33 else 34 return -1; 35 36 // return this.freq - o.freq; 37 } 38 39 } 40 41 public List<Integer> topKFrequent(int[] nums, int k) { 42 Map<Integer, Integer> map = new TreeMap<Integer, Integer>(); 43 for (int num : nums) { 44 if (map.containsKey(num)) 45 map.put(num, map.get(num) + 1); 46 else 47 map.put(num, 1); 48 } 49 PriorityQueue<Freq<Integer>> pq = new PriorityQueue<Freq<Integer>>(); 50 for (Integer key : map.keySet()) { 51 if (pq.getSize() < k) { 52 pq.enQueue(new Freq<Integer>(key, map.get(key))); 53 } else { 54 if (map.get(key).compareTo(pq.getQueue().freq) > 0) { 55 pq.deQueue(); 56 pq.enQueue(new Freq<Integer>(key, map.get(key))); 57 } 58 } 59 } 60 61 List<Integer> linkedList = new LinkedList<Integer>(); 62 while (!pq.isEmpty()) { 63 linkedList.add(pq.deQueue().e); 64 } 65 return linkedList; 66 } 67 68 public static void main(String[] args) { 69 PriorityQueueTest solution = new PriorityQueueTest(); 70 71 int[] nums = { 1, 2, 1, 2, 2, 3 }; 72 List<Integer> topKFrequent = solution.topKFrequent(nums, 2); 73 System.out.println(topKFrequent); 74 } 75 76 }