堆排序
package sort;
/**
* @author: tianhaichao
* @date: 2022/8/31 18:12
* @description:
*/
public class HeapSort {
// 通过无序数组构建大顶堆说明
// a(0)
// b c
// d e f(5) g
// h i j k l m
// 数组a b c d e f g h i j k l m 长度13 第一个非叶子节点是13/2-1 = 5 f
// 从f为父节点的子树开始,从右向左,从下到上一棵一棵子树调整,使得每棵子树都满足大根堆的规则-父节点大于所有子节点
// 子树调整的顺序依次是:flm ejk dhi cfglm bdehijk abcdefghijklm 也就是分别以fedc3ba为根节点的所有子树
// ps:对于多层子树,因为底层其实已经调整过了,所以如果顶层没有执行调整,可以不必从新调整底层,但如果上层做了调整,为了满足整整棵子树都是父节点大于子节点,所以需要从新比较、调整
/**
* @author: tianhaichao
* @date: 2022/9/1 15:54
* @description: 将无序数组构建成大顶堆后 执行排序
*/
public static void sort(int[] array) {
//【1】、构建大顶堆
// 根据数组,创建大根堆 从最后一颗子树开始循环 ( 因为此时的数组是无序的,所以需要循环每一棵子树,逐一调整)
// 最后一棵子树开始,从右到左,从下到上
// 最后一棵子树的下标算法 array.length/2 -1
// 每循环一次,调整一颗子树符合大根堆规则,循环结束,整棵树满足大根堆要求
for (int sonTreeParent = array.length / 2 - 1; sonTreeParent >= 0; sonTreeParent--) {
adjustHeap2(array, sonTreeParent, array.length);
// adjustHeap(array, sonTreeParent, array.length);
}
printBinaryTree(array);
//【2】、通过大顶堆进行堆排序
// i 每次需要调整为大顶堆的数组长度 0-i 个元素
for (int i = array.length - 1; i > 0; i--) {
// 交换堆顶元素和末尾元素,继续调整
int temp = array[0];
array[0] = array[i];
array[i] = temp;
// 此时的数组经过上面的循环子树调整,已经满足大顶堆的规则,可以直接从堆顶下沉最小值,上浮本次查找范围的最大值到堆顶
adjustHeap(array, 0, i);
}
}
/**
* @author: tianhaichao
* @date: 2022/9/1 15:52
* @description:为了少几次交换,通过tempIndex记录位置,直至小值沉底后做的交换,比较不好理解
*/
public static void adjustHeap(int[] array, int startTreeParent, int end) {
int temp = array[startTreeParent];
int tempIndex = startTreeParent;
// 每循环一次,比较出当前树的一组父子的大小关系(例如:abc 、bde、crt abcdert分别为一组父子关系)
// a
// b c
// d e r t
for (int son = startTreeParent * 2 + 1; son < end; son = son * 2 + 1) {
// 存在右节点,且右节点比左节点大
if (son + 1 < end && array[son + 1] > array[son]) {
son = son + 1;
// TODO i++ 和i= i+1 的区别
}
// 此时的son是该组父子节点中最大的孩子节点
if (array[son] > temp) {
// 对son节点值和父亲节点做交换,此时改变了son节点为父节点的子树,所以循环要继续调整son为父节点的子树结构
if (array[son] > array[startTreeParent]) {
// 当层子树,已经赋值过一次了,一定要确保第二次比temp大的值确实比已经被赋过值得startTreeParent大,才能赋值
array[startTreeParent] = array[son];
} else {
array[tempIndex] = array[son];
}
// 此时父节点的值临时和该孩子节点的值交换(假交换,所以只做下标赋值),通过后面对下层子树的循环,该小值可能会继续下沉
tempIndex = son;
} else {
// 没有做调整,孩子节点为父节点的子树结构也不用调整,直接跳出循环
break;
}
}
// 此时tempIndex是startTreeParent值下沉的最终位置,进行真正的赋值,完成交换
array[tempIndex] = temp;
}
/**
* @author: tianhaichao
* @date: 2022/9/1 15:38
* @description: 直接做交换,比较好理解的一种调整写法
*/
public static void adjustHeap2(int[] array, int startTree, int end) {
// 每循环一次,比较出当前树的一组父子的大小关系(例如:abc 、bde、crt abcdert分别为一组父子关系)
// a
// b c
// d e r t
for (int startTreeParent = startTree; startTreeParent < end; ) {
int son = startTreeParent * 2 + 1;
// 存在右节点,且右节点比左节点大
if (son + 1 < end && array[son + 1] > array[son]) {
son = son + 1;
}
// 此时的son是该组父子节点中最大的孩子节点
if (son + 1 < end && array[son] > array[startTreeParent]) {
// 此时父节点的值临时和该孩子节点的值交换
int temp = array[startTreeParent];
array[startTreeParent] = array[son];
array[son] = temp;
// 对son节点值和父亲节点做交换,此时改变了son节点为父节点的子树,所以循环要继续调整son为父节点的子树结构
startTreeParent = son;
} else {
// 没有做调整,孩子节点为父节点的子树结构也不用调整,直接跳出循环
break;
}
}
}
public static void printBinaryTree(int[] array) {
int n = 0;
for (int left = 0; left < array.length; left = left * 2 + 1) {
// 以第一个左节点为开始,循环一层
if (left == 0) {
System.out.print(array[0]);
}
// 每一层结尾的下标可以是left+2*n
for (int elementLayer = left; elementLayer < left + 2 * n; elementLayer++) {
if (elementLayer > array.length - 1) {
break;
}
System.out.print(array[elementLayer]);
System.out.print(" ");
}
System.out.print("\n");
//层计数
n++;
}
}
public static void main(String[] args) {
final int[] array = {4, 6, 8, 5, 9, 2, 23, 1, 0, 10};
HeapSort.printBinaryTree(array);
System.out.println();
HeapSort.sort(array);
System.out.println();
HeapSort.printBinaryTree(array);
}
}
堆排序:堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。
堆是具有以下性质的完全二叉树
大顶堆:每个结点的值都大于或等于其左右孩子结点的值
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 2*(i+1)
小顶堆:每个结点的值都小于或等于其左右孩子结点的值
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
完全二叉树,一棵二叉树,从上到下,从左到右,填满,如果有一个子节点的节点,那肯定是最右边且最下边的节点。
分两步:
一、构造初始堆
将一个数组中无序的数据,整理成为一组符合大顶堆或小顶堆规则的数据。
二、循环下沉
根据大顶堆规则,根节点肯定是最大的,使其与最后一个节点交换位置,然后抛弃最后一个节点,继续循环调整以根节点为父节点的子树,使其满足大顶堆规则,再用根节点与倒数第二个节点的值进行交互,此时舍弃最后两个排好序的节点,继续循环调整以根节点为父节点的子树,使其满足大顶堆规则,以此类推。
时间复杂度是O(nlogn),参见 https://blog.csdn.net/qq_39032310/article/details/87470670


浙公网安备 33010602011771号