堆分为:
- 大根堆
- 小根堆
我先简单说一下大根堆的定义
可以将该一维数组视为一棵完全二叉树,大根堆的最大元素存放在根结点,且其任一非根结点的值小于等于其双亲结点值。
小根堆的话则和大根堆完全相反,即:小根堆的最小元素存放在根结点,且其任一非根结点的值大于等于其双亲结点值。
堆排序的基本思想是:
- 1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;
- 2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;
- 3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。
最后,就得到一个有序的序列了
下面我们通过一个简单的例子去了解一下:
假设我们的待排序数组为[4,6,8,5,9]
我们的起始节点即为最后一个非叶子结点,对应的下标为:length/2-1
5/2-1=1,所以我们从6开始
找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换,记住:我们每次都是和最大的子节点交换。
此时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
我们非叶子结点交换完成之后,即i=0之后,输出堆顶元素(9),开始进行第二轮堆排序,将堆顶元素与末尾元素进行交换,使末尾元素最大。
然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
-
(1)将堆顶元素9和末尾元素4进行交换,同时让length-1,即现在length变为4,我们待排序数组变为:[4,6,8,5],起始点:i=length/2-1=4/2-1=1,即从6开始进行调整,调整完成之后i-1=1-1=0,即下一个调整的点为nums[0]=4。
-
(2)4和8进行交换
由于i=0,所以输出堆顶元素:8,于是我们拿到了第二大元素,将最后一个元素5和8交换位置,同时length-1=4-1=3。
开始第三轮的堆排序,第三轮排序的数组为:[5,6,4]
起始下标为:3/2-1=0,即从nums[0]=5开始。
(1)5和6交换位置
由于i=0,我们拿到了第三大元素:6,输出6,交换6和4的位置,同时length-1=3-1=2;
开始第四轮堆排序,排序数组为:[4,5]
起始下标为:i=2/2-1=0,即从nums[0]=4,4和5交换位置
由于i=0,所以输出我们第四大元素:5,length-1=2-1=1,由于length<=1,所以输出最后一个元素:4,堆排序结束。
大顶堆代码实现:
/**
* 堆排序
*
* @param arr
*/
public static void heapSort(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
int len = arr.length;
// 构建大顶堆,这里其实就是把待排序序列,变成一个大顶堆结构的数组
buildMaxHeap(arr, len);
// 交换堆顶和当前末尾的节点,重置大顶堆
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0, len);
}
}
private static void buildMaxHeap(int[] arr, int len) {
// 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
for (int i = (int) Math.floor(len / 2) - 1; i >= 0; i--) {
heapify(arr, i, len);
}
}
private static void heapify(int[] arr, int i, int len) {
// 先根据堆性质,找出它左右节点的索引
int left = 2 * i + 1;
int right = 2 * i + 2;
// 默认当前节点(父节点)是最大值。
int largestIndex = i;
if (left < len && arr[left] > arr[largestIndex]) {
// 如果有左节点,并且左节点的值更大,更新最大值的索引
largestIndex = left;
}
if (right < len && arr[right] > arr[largestIndex]) {
// 如果有右节点,并且右节点的值更大,更新最大值的索引
largestIndex = right;
}
if (largestIndex != i) {
// 如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换
swap(arr, i, largestIndex);
// 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
heapify(arr, largestIndex, len);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}