堆排序
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