import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
// 要求将数组进行升序排序
// int arr[] = {4, 6, 8, 5, 9};
// heapSort(arr);
// System.out.println(Arrays.toString(arr));
// 800w 数据测试
int[] arr = new int[5];
arr= new int[]{4, 6, 8, 5, 9};
long time1 = System.currentTimeMillis();
heapSort(arr);
long time2 = System.currentTimeMillis();
System.out.println("堆排序实现大顶堆排序800W数据:" + (time2 - time1) + "毫秒");
System.out.println(Arrays.toString(arr));
}
/**
* 构建堆排序(最终调用)
* @param arr 传入的数组
*/
public static void heapSort(int[] arr) {
int temp = 0;
// 分步测试
// adjustHeap(arr, 1, arr.length);
// System.out.println("第一次" + Arrays.toString(arr)); // 4, 9, 8, 5, 6
// adjustHeap(arr, 0, arr.length);
// System.out.println("第二次" + Arrays.toString(arr)); // 9, 6, 8, 5, 4
// 完整步骤
// 将无序序列构建成的一个堆,根据升序或者降序,选择大顶堆或者小顶堆 为社么是arr.length/2-1 这是因为从下到上依次
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
// 将堆顶元素与末尾元素交换,将最大元素 "沉"到数组末端
// 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,(就这样反复执行 【调整 -> 交换】,直到整个序列有序)
// 时间复杂度 为线性 O(nlogn)
for (int j = arr.length - 1; j > 0; j--) {
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr, 0, j);
}
}
/**
* 将一个数组(二叉树),调整成一个大顶堆
* 功能:完成将 以 i 对应的非叶子节点的树调整成大顶堆
* eg:第一次:int arr[] = {4, 6, 8, 5, 9}; => adjustHeap(i=1) => 得到 {4, 9, 8, 5, 6}
* eg:第一次:int arr[] = {4, 9, 8, 5, 6}; => adjustHeap(i=0) => 得到 {9, 6, 8, 5, 4}
* @param arr 待调整的数组
* @param i 表非叶子节点在数组中的索引
* @param length 表对多少个元素进行调整(length会逐渐减少,因为被调整好的增加了)
*/
public static void adjustHeap(int[] arr, int i, int length) {
// 先取出当前元素的值,报错在临时变量中
int temp = arr[i];
// 开始调整
// 故:k = 2*i+1; 表示 k 是 i节点的左子节点
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {// k=2*k+1 ,k 左子节点的左子节点
if (k + 1 < length && arr[k] < arr[k + 1]) {// 说明左子节点的值,小于右子节点的值
k++;// k 指向右子节点
}
if (arr[k] > temp) {// 若子节点大于父节点的值
arr[i] = arr[k]; // 把较大的值赋值给当前节点
i = k;// 指向 k,继续比较(移动)
} else {
break;
}
}
// 当for 循环结束后,已经将以 i 为父节点的树的最大值,放在最顶点(局部)
arr[i] = temp;// 将 temp放到调整后的位置
}
}
X9-1、java数据结构---堆排序【2021-1-2】 - 简书 (jianshu.com)
别人的代码
这是测试代码
测试的数组是这样的 初始状态是无序堆
这里我们要进行升序排序 那么就需要将无序堆排序成大顶堆
1.第一次排序成大顶堆
因为主函数是只调用了heapSort()方法 所以我们在这里打断点
此时的值
点击下一步后进入到了这里
经过了int temp=0后的值
这个if代码是干嘛的 其实是进行大顶堆排序的
我们怎么把一个无序堆排序为大顶堆
首先先从最下面开始 所以就从这一棵子树开始排序 所以循环里面的i=arr.length/2-1 这里就是 1 注意arr.length/2是整除 所以就算结果是5/2=2.5 但是会取2 所以结果是1
所以第一次的排序是从非叶子结点1开始排序
然后一直点击下一步 就进入了if语句里面的代码
也就是这里面
此时的值
根据这个可以知道 我们上述的推理是正确的 第一个需要排序的就是结点6的子树
这一步又是什么意思
因为要进行堆的跳转 肯定要交换结点 所以设置了一个temp temp指向的是一个子树的根节点
跳转到下一步(就是经过了temp这一步后 )
此时的值
再进行下一步 这里面的if就是进行排序了 就是判断子树的字节点和根节点的大小比值 然后根据情况进行结点交换
此时的值
根据代码可知 if里面比较的是
第一比较的是 有没有右字节点
第二步比的是 左字节点是不是小于右字节点
为什么要比较 因为此时的i指向的是k 也就是左节点(此时的i是1 指向的结点值为6)
如果确实左节点小于右结点 那么就让k++ 也就是让arr[k]指向结点值较大的值
所以if语句的作用就是找出左右结点哪一个的值更大
经历了上面的if之后
到了这里
根据此时的值可知
所以if语句里面的值确实增加了arr[k]指向了值更大的9
然后再向下运行 就会执行完这个if else语句 这个if语句执行了些什么呢
1.if语句里面是进行值的比较 比较的对象是 我们在之前的if语句里面找到的最大的字节点 和子树的根节点进行比较
如果满足 那么就让根结点的值变成最大值的字节点 然后下标也跟着变化 (其实就是让根节点变成和最大值字节点一摸一样 本质就是替换结点)
就是变成了这样 只不过和图里面不一样的是 4结点还没从9变成6
到达这里 又开始了for循环
此时的值
点击下一步的时候
就直接到了这里 说明for循环直接跳出来了 因为k的值大于了length 此时的k值是9 length是5
经过了这一步后 原来的结点9就变成了结点6了
完完全全变成这样了
然后再向下执行(也就是heapSort的for循环的第一次循环结束 开始进行下一个循环 也就是开始调整下一棵树了)
此时的值
点击下一步后 就变成了这样
i=0 说明应该调整这一棵子树了
然后就是相同的过程了 这里就不细讲了
反正调整了之后就变成这样了
全部子树
但是还有一个问题 因为不断的调整导致了树的混乱
这里的结点6应该在顶点 所以需要再调整一下
变成这样
那这是如果变成这样的呢 这一步是在哪里进行的
因为结点1的子树调整完了
然后又会再运行一次for循环 此时的i就变成了0了
也就是该调整0号子树了 然后就进入adjustHead函数
当经历了k=2*0+1的循环后
所有的值就变成了这样
为什么i变成了1 因为 经历了这一步
然后再for循环k就变成了3 ( 2*1+1)
也就是再次调整i=1的子树
也就是说这次的for循环就是调整这样
上面的这次调整是在这里 就是i=0的根结点所在的子树 调整完了之后
因为还没跳出for进行array[i]=temp
所以此时的数组是这样的
最后会执行这段代码 下面这个代码在heapSort()里面 是最后一行
这三步是干嘛的 将第一个元素和最后一个元素交换位置 保证最大的元素在数组最后
交换好了之后 会再次按照上述的操作调整大顶堆 但是不同的是 会去掉最后一个元素进行调整
也就是不看结点9 这既是这段代码的含义
i是从哪一个结点开始调整
每调整完一次 j就会减小1 也就是屏蔽最后一个结点
至此所有的就讲完了