前言基础:
1、堆排序的时间复杂度为O(nlogn),空间复杂度为O(1)。
2、推排序是不稳定的排序算法。
3、每次排序保证了后i个元素是有序的,第后i个元素所在的位置是其最后所在的位置,每次将区域内最大的元素放在倒数第i个位置上。
4、堆排序大致分为整理堆和排序两个过程。
下述代码都是索引从0开始的,所以:
1、双亲节点:(i-1)/2。
2、左孩子:2*i+1。
3、右孩子:2*i+2.
4、是否为叶子节点的条件 2 * i + 1 < n 或 2 * i + 2 <= n (n为数组的长度)
如果索引下标从1开始 :
大顶堆的性质
1、最大的元素是数组的第一个元素。
2、实现的底层是一个完全二叉树。
3、每一个孩子节点的值都小于其父节点的值。
4、保证了父节点的值一定大于子节点,但是层数之间没有明显的大小关系。如下图13在第三层但是其比第四层的17小。
5、添加元素:每次只将元素添加到最后,然后不断上浮整理堆。
6、删除元素:只能取出堆顶,即最大的元素。然后将最后一个元素赋值给第一个元素,最后对该元素进行下沉整理堆。
整理堆
从第一个非叶子节点开始,向前整理,并用该节点与其两个孩子节点进行比较,如果该双亲节点比其最大的孩子节点小,就交换二者的位置。直到该节点大于两个孩子节点的值或该节点是叶子节点。
排序
每次排序的时候取出[0,i]中第一个元素,即最大的元素也叫堆顶,其与数组倒数第i个元素进行交换,然后再次对堆进行整理,保证大顶堆每次取出元素之后仍然在[0,n-i]上保持大顶堆的性质。
代码:
public class HeapSort {
public HeapSort() {
}
//时间复杂度是O(nlogn)
public static <E extends Comparable<E>> void sort(E arr[]) {
if(arr.length<=1) return;
//从第一个非叶子节点的元素开始进行堆整理。
/*arr.length-2解释:
* 因为某个节点的双亲是(i-1)/2,所以设该i就是数组中的最后一个元素,即arr.lenth-1,所以其双亲为(arr.length-1-1)/2
*/
for (int i = (arr.length - 2) / 2; i >= 0; i--) {
sitDown(arr, i, arr.length);
}
//将元素添加到原数组,并且不断构建堆
for (int i = arr.length - 1; i >= 0; i--) {
Util.swap(arr, 0, i);
sitDown(arr, 0, i);
}
}
private static <E extends Comparable<E>> void sitDown(E[] arr, int i, int n) {
while (2 * i + 1 < n) {
int left = 2 * i + 1;
//找到左右孩子最大的那个索引的下标,最终的left指向的就是要交换的那个孩子的索引
if (left + 1 < n && arr[left].compareTo(arr[left + 1]) < 0)
left++;
//如果其比最大的那个还要大,则不需要交换了,直接退出
if (arr[i].compareTo(arr[left]) >= 0)
break;
//否则执行交换逻辑
Util.swap(arr, i, left);
//让i等于应该交换的那个元素的下标
i = left;
}
}
}