堆排序

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

 

posted @ 2022-09-17 16:22  田海超  阅读(21)  评论(0编辑  收藏  举报