堆,完全二叉树
满二叉树属于完全二叉树。
不是满二叉树,新的一层从做往右依次补齐。属于完全二叉树。
堆可以用数组实现。可以把一个数组理解为完全二叉树。
给出一个数组,可以在逻辑上脑补出一个完全二叉树。0 的父节点还是自己。(特殊节点)
某个节点:i
左孩子:2 * i +1
右孩子:2 * i + 2
父节点:(i-1)/ 2
堆-是撒东西
大根堆——>就是完全二叉树,在这颗完全二叉树中,任何子树的最大值,都是头部。
小根堆——>就是完全二叉树,在这颗完全二叉树中,任何子树的最小值,都是头部。
问题来了:怎么用数组,变成大根堆?
heapinsert
你已经形成一个堆,要加入一个新的节点,你要经历向上依次比对,这个值要足够大,就往上跑,跑到一个你不比父节点大的时候,你就停。这个过程就是 heapinsert 过程。-- 这就是形成大根堆的过程。
heapify
一个大根堆,假设数组中有个数变小了,怎么重新调整成大根堆?
这个变化的值,找到他左右两个孩子中最大的值,该最大值比变化的值大,和他交换。
当整个完全二叉树是整个数组时,整个 heapsize 就是数组大小。
但也有可能 0 ~ i 范围形成二叉树。堆最大的情况可能是整个数组大小,整个数组从 0~i 这一段才是堆。于是给了他一个参数,heapsize ,是代表堆上一共有多少个数?一定不会比数组的个数大。
如果有越界,说明已经是叶子节点了。停哪儿就可以了。
package suanfa;
public class HeapSort {
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) { //这里执行完,就是大根堆了
heapInsert(arr, i);
}
int heapSize = arr.length;
swap(arr, 0, --heapSize);
while (heapSize > 0) {
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
public static void heapify(int[] arr, int index, int size) {
int left = index * 2 + 1; // 左孩子,为什么不弄右孩子,两个孩子有一个就行了
while (left < size) { // 左孩子不越界下,一直执行
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
// 右孩子不越界,且右孩子>左孩子。两个孩子中选择最大的
largest = arr[largest] > arr[index] ? largest : index; // 最大的跟父节点比较
if (largest == index) { // 我、左右孩子中最大值是自己,直接弹出。
break;
}
swap(arr, largest, index); // 前面比较完之后,交换
index = largest; // 最大值变成我自己。
left = index * 2 + 1; // 判断是否越界
}
}
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) { // 只要比父节点大,就一直循环判断
swap(arr, index, (index - 1) / 2); // 交换,并继续向上判断
index = (index - 1) / 2; // index 赋值给父节点
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
public static void main(String[] args) {
int[] arr = { 2, 0, 12, -4, 9, 81 };
System.out.println(java.util.Arrays.toString(arr));
heapSort(arr);
System.out.println(java.util.Arrays.toString(arr));
}
}