堆的概念
堆是一种数据结构的统称,其在组织结构上总是一棵完全二叉树。堆中的元素总满足
- 堆中某个结点的值总是不大于或不小于其父结点的值,也可以认为是堆的根结点以及其任意一个子树的根结点都是对应树中的最大或最下元素
- 根结点最大的元素是大根堆,根结点最小的元素是小根堆
堆的结构图示
- 堆可以用二叉树实现
- 因为堆是一个完全二叉树,所以堆也可以用数组实现
当用数组实现堆时,堆的根结点对应数组索引为1的元素,然后依次的每一层,从左到右,都顺序对应数组中的每一个元素,结构如下所示,其中1,2,3…等是数组索引。
在这种情况下,索引 i 位置对应结点的情况如下:
- 左孩子:2 * i = i << 1
- 右孩子:2 * i + 1 = (i<<1) | 1
- 父亲节点:i / 2 = i >>1
堆的操作
- 有一组数,但每次只给定一个数,将这组数组织成大根堆。
用一个数组arr来存储堆,用heapSize来表示当前堆的大小,初始时heapSize=0。当接受到第一个元素时,将其放在arr[heapSzie+1]位置,即arr[1] (堆的根结点),随后来的每一个元素都放在heapSize+1位置,然后做如下操作
- 比较新插入的元素与其父节点的大小,如果比父节点大,则交换它和父节点的位置,否则不做操作,重复这个操作,直至它不比父节点大或者没有父节点。
- 这个过程其实是向堆中插入一个元素,并保持堆结构不变
将3,2,4,5,3,2按上述步骤组织成堆的过程示意图如下:
2. 给定一个大根堆,返回堆中最大值,并删去最大值,并保证删除后的结构依然是大根堆
堆的根结点存放在arr[1]位置,所以返回arr[1],然后将arr[heapSize]的元素放到堆顶,heapSize- -,然后执行heapif操作,将其调整为大根堆
- 比较堆顶元素与其两个子结点的大小,并将其与较大的子结点交换位置,然后重复这个操作,直至其不比子结点小或者没有子结点
heapif是删除堆顶元素后,如何保持堆结构的操作
示意图如下:
3. 堆排序
堆的java代码实现
public class Code02_Heap {
public static class MyMaxHeap {
private int[] heap;
private final int limit;
private int heapSize;
public MyMaxHeap(int limit) {
heap = new int[limit];
this.limit = limit;
heapSize = 0;
}
public boolean isEmpty() {
return heapSize == 0;
}
public boolean isFull() {
return heapSize == limit;
}
public void push(int value) {
if (heapSize == limit) {
throw new RuntimeException("heap is full");
}
heap[heapSize] = value;
// value heapSize
heapInsert(heap, heapSize++);
}
// 用户此时,让你返回最大值,并且在大根堆中,把最大值删掉
// 剩下的数,依然保持大根堆组织
public int pop() {
int ans = heap[0];
swap(heap, 0, --heapSize);
heapify(heap, 0, heapSize);
return ans;
}
// 新加进来的数,现在停在了index位置,请依次往上移动,
// 移动到0位置,或者干不掉自己的父亲了,停!
private void heapInsert(int[] arr, int index) {
// [index] [index-1]/2
// index == 0
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
// 从index位置,往下看,不断的下沉
// 停:较大的孩子都不再比index位置的数大;已经没孩子了
private void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1;
while (left < heapSize) { // 如果有左孩子,有没有右孩子,可能有可能没有!
// 把较大孩子的下标,给largest
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
// index和较大孩子,要互换
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
public static class RightMaxHeap {
private int[] arr;
private final int limit;
private int size;
public RightMaxHeap(int limit) {
arr = new int[limit];
this.limit = limit;
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == limit;
}
public void push(int value) {
if (size == limit) {
throw new RuntimeException("heap is full");
}
arr[size++] = value;
}
public int pop() {
int maxIndex = 0;
for (int i = 1; i < size; i++) {
if (arr[i] > arr[maxIndex]) {
maxIndex = i;
}
}
int ans = arr[maxIndex];
arr[maxIndex] = arr[--size];
return ans;
}
}
}