堆
堆结构就是用数组实现的完全二叉树结构
完全二叉树中如果每棵子树的最大值都在顶部就是大跟堆
完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
以0开始
左孩子:2 * i + 1
右孩子:2 * i + 2
父 (i - 1) / 2
另外一种表示:下标为0弃掉 方便使用位运算
左孩子2 * i 》》》》》对应的位运算 i<<1
右孩子2 * i + 1 》》》》》对应的位运算 i<<1 | 1
父 i/2 》》》》》对应的位运算 i>>1
实现:
package com.zy.base.class004;
import java.util.Arrays;
public class MyMaxHeap {
public static void main(String[] args) {
MyMaxHeap myMaxHeap = new MyMaxHeap(10);
myMaxHeap.push(1);
myMaxHeap.push(9);
myMaxHeap.push(8);
myMaxHeap.push(10);
System.out.println(myMaxHeap);
myMaxHeap.pop();
System.out.println(myMaxHeap);
}
private int[] heap;
private int limit;
private int heapSize;
public MyMaxHeap(int limit) {
this.heap = new int[limit];
this.limit = limit;
this.heapSize = 0;
}
public boolean isEmpty() {
return heapSize == 0;
}
public boolean isFull() {
return heapSize == limit;
}
public void push(int value) {
if (this.isFull()) {
throw new RuntimeException("heap is full");
}
heap[heapSize] = value;
heapInsert(heap, heapSize++);
}
// 用户此时,让你返回最大值,并且在大根堆中,把最大值删掉
// 剩下的数,依然保持大根堆组织
public int pop() {
int res = heap[0];
swap(heap, 0, --heapSize);
heapify(heap, 0, heapSize);
return res;
}
// 从index位置,往下看,不断的下沉,
// 停:我的孩子都不再比我大;已经没孩子了
private void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1;
while (left < heapSize) {
// 左右两个孩子中,谁大,谁把自己的下标给largest
// 右 -> 1) 有右孩子 && 2)右孩子的值比左孩子大才行
// 否则,左
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
swap(arr, index, largest);
index = largest;
left = index * 2 + 1;
}
}
private void heapInsert(int[] arr, int index) {
// arr[index]
// arr[index] 不比 arr[index父]大了 , 停
// index = 0;
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private void swap(int[] arr, int index1, int index2) {
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
@Override
public String toString() {
return "MyMaxHeap{" +
"heap=" + Arrays.toString(heap) +
", limit=" + limit +
", heapSize=" + heapSize +
'}';
}
}
MyMaxHeap{heap=[10, 9, 8, 1, 0, 0, 0, 0, 0, 0], limit=10, heapSize=4}
MyMaxHeap{heap=[9, 1, 8, 10, 0, 0, 0, 0, 0, 0], limit=10, heapSize=3}
堆排序实现
如果一个一个数字传输
package com.zy.base.class004;
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int[] arr = {5, 1, 10, 6, 2, 4, 8, 3, 9};
new HeapSort().heapSort(arr);
System.out.println(Arrays.toString(arr));
}
// 堆排序额外空间复杂度O(1)
public void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int len = arr.length;
MyMaxHeap myMaxHeap = new MyMaxHeap(len);
// O(N*logN)
for (int i = 0; i < arr.length; i++) { // O(N)
myMaxHeap.heapInsert(arr, i); // O(logN)
}
int heapSize = arr.length;
myMaxHeap.swap(arr, 0, --heapSize);
// O(N*logN)
while (heapSize > 0) { // O(N)
myMaxHeap.heapify(arr, 0, heapSize); // O(logN)
myMaxHeap.swap(arr, 0, --heapSize); // O(1)
}
}
}
如果是直接给了一个数组,有很多数组,那么可以优化:
package com.zy.base.class004;
import java.util.Arrays;
public class HeapSortOptimization {
public static void main(String[] args) {
int[] arr = {5, 1, 10, 6, 2, 4, 8, 3, 9};
new HeapSortOptimization().heapSort(arr);
System.out.println(Arrays.toString(arr));
}
// 堆排序额外空间复杂度O(1)
public void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int len = arr.length;
MyMaxHeap myMaxHeap = new MyMaxHeap(len);
// O(N*logN)
// 从最后一个开始,整理数组,调整为大顶堆
for (int i = len - 1; i >= 0 ; i--) {
myMaxHeap.heapify(arr, i, len);
}
int heapSize = len;
myMaxHeap.swap(arr, 0, --heapSize);
// O(N*logN)
// 然后不断的将最大值调整到最后一个元素,然后调整大顶堆,始终保持着大顶堆的特征
while (heapSize > 0) { // O(N)
myMaxHeap.heapify(arr, 0, heapSize); // O(logN)
myMaxHeap.swap(arr, 0, --heapSize); // O(1)
}
}
}
堆排序总结:
1.先让整个数组都变成大根堆结构,建立堆的过程:
1)从上到下的方法,时间复杂度为O(N*logN)
2)从下到上的方法,时间复杂度为O(N)
2.把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N*logN)
3.堆的大小减小成0之后,排序完成
与堆有关的题目
已知一个几乎有序的数组。几乎有序是指,如果把数组排好序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来书是比较小的。
请选择一个合适的排序策略,对这个数组进行排序。
package com.zy.base.class004;
import java.util.Arrays;
import java.util.PriorityQueue;
public class SortArrayDistanceLessK {
public static void main(String[] args) {
int maxSize = 100;
int maxValue = 100;
int[] arr = randomArrayNoMoveMoreK(maxSize, maxValue, 5);
System.out.println(Arrays.toString(arr));
sortedArrDistanceLessK(arr, 5);
System.out.println(Arrays.toString(arr));
}
public static void sortedArrDistanceLessK(int[] arr, int k) {
if (k == 0) {
return;
}
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
int index = 0;
for (; index <= Math.min(k, arr.length - 1); index++) {
priorityQueue.add(arr[index]);
}
int i = 0;
for (; index < arr.length; index++) {
priorityQueue.add(arr[index]);
arr[i++] = priorityQueue.poll();
}
while (!priorityQueue.isEmpty()) {
arr[i++] = priorityQueue.poll();
}
}
// for test
public static int[] randomArrayNoMoveMoreK(int maxSize, int maxValue, int K) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
// 先排个序
Arrays.sort(arr);
// 然后开始随意交换,但是保证每个数距离不超过K
// swap[i] == true, 表示i位置已经参与过交换
// swap[i] == false, 表示i位置没有参与过交换
boolean[] isSwap = new boolean[arr.length];
for (int i = 0; i < arr.length; i++) {
int j = Math.min(i + (int) (Math.random() * (K + 1)), arr.length - 1);
if (!isSwap[i] && !isSwap[j]) {
isSwap[i] = true;
isSwap[j] = true;
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
return arr;
}
}