堆排序详解
说明
- 堆排序基于堆的特性,速度较快,效率较高,平均时间复杂度为线性对数阶
- 先说明大顶堆和小顶堆
- 大顶堆是指当前二叉树的父节点对应的值总是大于子节点对应的值,及arr[i] >= arr[ 2 * i + 1] 并且arr[i] >= arr[ 2 * i + 2]总成立,不论是根节点还是其子树
- 小顶堆刚好相反,其父节点对应的值全部小于等于子节点
- 而堆排序只是基于大顶堆和小顶堆来实现,具体使用哪个依赖于升序排序还是降序排序,如果是升序则使用大顶堆,如果是降序则使用小顶堆
- 堆排序实际操作的并不是二叉树,而是数组,因为二叉树可以顺序存储,及使用数组来模拟二叉树,当前节点的子节点对应索引为 2 * i + 1 和 2 * 1 +2
- 要完成堆排序,首先需要将一颗完全二叉树调整成一个大顶堆,因为大顶堆的根节点是数组元素中最大的,再将这个根节点对应的元素和数组中的最后一个元素进行交换位置,然后对数组中剩余的 n - 1个元素再调整成为大顶堆,再交换位置,依次进行,直到数组中元素全部调整完
- 完全二叉树调整大顶堆思路:
- 假设数组长度为len , 当前要调整的子树对应的节点在数组中索引为 i ,先判断当前节点的左右子节点的大小,让子节点中较大的数和当前节点比较,如果子节点的数大于当前节点,说明需要交换
- 交换完成后,重置 i ,然后循环调整
- 实现调整大顶堆的方法后,按照总左到右,从下到上的顺序将当前二叉树进行大顶堆的调整
- 即先找到最左下角的非叶子节点开始调整,依次循环,直到调整到数组的第一个元素为止
- 第一次调整完后,交换数组的第一个元素和最后一个元素,即将最大的元素移动到数组的最后
- 然后重置数组的大小,即下次调整时数组长度 - 1,让最大的数不参与调整,将剩下的数调整完后再交换顺序
- 源码及分析如下
源码及分析
package algorithm.tree;
import java.util.Arrays;
/**
* @author AIMX_INFO
* @version 1.0
*/
public class HeapSort {
public static void main(String[] args) {
System.out.println("堆排序...");
int[] arr = {2, 1, 45, 34, 77, 23};
heapSort(arr);
}
public static void heapSort(int[] arr) {
//定义临时变量tmp用于交换
int tmp = 0;
//按照从左到右,从下到上的顺序,依次调整大顶堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
//将调整后的大顶堆中的最大值移动到数组的最后
for (int i = arr.length - 1; i > 0 ; i--) {
tmp = arr[i];
arr[i] = arr[0];
arr[0] = tmp;
adjustHeap(arr,0,i);
}
System.out.println(Arrays.toString(arr));
}
//编写核心的调整大顶堆方法
/**
* @param arr 要调整的数组
* @param i 非叶子节点的索引
* @param length 数组元素长度
*/
public static void adjustHeap(int[] arr, int i, int length) {
//定义变量保存要调整的非叶子节点的元素
int tmp = arr[i];
//循环调整数组,将其调整为一个大顶堆
for (int k = 2 * i + 1; k < length; k = k * 2 + 1) {
//判断当前非叶子节点左右节点大小,让k指向当前非叶子节点的较大节点索引
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++;
}
//如果当前叶子节点的值大于非叶子节点的值,则交换顺序
if (arr[k] > tmp) {
arr[i] = arr[k];
//并将 k 指向当前较大的节点,开始下一次循环比较
i = k;
} else {
break;
}
//for循环结束后,则以 i 为根的子树已经成为一颗大顶堆数
arr[i] = tmp;
}
}
}