堆通常可以理解为树状的数据结构.每个堆都是一棵完全二叉树.在计算机中,堆可以用数组模拟出来.第0个元素作为堆的根,叶子节点的位置为 i * 2 + 1(左叶子) 和 i * 2 + 2(右叶子),i是根节点的index.我们通常把堆分为大根堆和小根堆.
大根堆:每棵树以及树内包含的子树,根节点的数值都大于叶子节点的数值.
小根堆则刚好相反,每棵树及树内包含的子树,根节点的数值都小于叶子节点.
当随机给我们一个数组,我们可以根据堆生成的规则确认根节点和叶子节点都是哪些,但数组内的数值分布可能既不满足大根堆的分布也不满足小根堆的分布.
将一个数组转换成大根堆(HeapInsert)
首先假定数组中的一部分index = 0 ~ index = i 是满足大根堆条件的,最初的时候i = 0,也就是第一个节点本身满足大根堆条件.
i增大1,此时变为0和1是大根堆范围.0是根节点,1是叶子节点.
验证1的值是否大于0位置的值,如果大于,则两个位置数值交换.
如果0位置还有父节点,迭代执行比对和交换的动作
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;
}
}
建立一棵大根堆的复杂度是O(N).
Heapify
现有大根堆中,其中一个节点的值变小了.改变之后,以该节点为根节点的子树就不满足大根堆的定义了,将其重新调整为一个大根堆的过程就是Heapify.
public static void heapify(int [] arr , int index, int heapSize){
int left = index * 2 + 1;
while (left < heapSize){
//选出左子节点和右子节点之间的最大值
int largest = (left + 1) < heapSize && arr[left] < arr[left + 1] ? left + 1 : left;
//子节点最大值同index本身比较选大的,此时的largest是三者比较的最大值
largest = arr[index] > arr[largest] ? index : largest;
//三者比较的最大值是index本身,直接break,不需要下沉
if (largest == index){ break; }
//如果不满足break条件,下沉.变换坐标.
swap(arr,largest,index);
index = largest;
left = index * 2 + 1;
}
}
堆排序
1-将要排序的数组变成大根堆(小根堆)
2-将堆的根节点同最后一个叶子节点交换位置
3-堆的范围缩小1,同时执行heapify将变化后的数组重新变为大根堆(小根堆).
4-重复2/3步骤
public static void heapSort(int [] arr){
if (arr.length < 2 && arr == null) { return; }
for (int i = 0; i < arr.length; i++){
heapInsert(arr,i);
}
int size = arr.length;
swap(arr,0, --size);
while (size > 0){
heapify(arr,0,size);
swap(arr,0, --size);
}
}
堆排序的时间复杂度是O(N log N)