堆排序

/**
 * 【堆】
 *      堆的概念:堆是一棵顺序存储的完全二叉树。
 *
 *      堆分为大根堆和小根堆:
 *          大根堆:每个节点的值不小于等于其左、右孩子的值
 *          小根堆:每个节点的值不大于等于其左、右孩子的值
 *
 * 【堆排序】
 *
 *  1)概念:指利用堆的特性将待排序的序列进行排序。
 *
 *  2)过程:
 *      1)将一个待排序的序列构造成一个堆:从最后一个非叶子结点向上遍历,直到所有的非叶子节点都遍历完毕(筛选法)。
 *      2)移走堆顶元素后,将剩余的元素再次构造成一个新的堆。
 *
 *  3)大根堆的排序:
 *      1)将待排序的n个元素构造成一个大根堆。
 *      2)移走堆顶元素(即:将堆顶元素与堆数组的末尾元素进行交换,此时末尾元素为最大值)。
 *      3)将剩余的n-1个元素重新构造成一个大根堆,重复上面的步骤,直到剩余的元素只有一个时,排序完成。
 *
 * 4)说明:将数组构造成初始堆时,若想升序排列构造大根堆,若想降序排列则构造小根堆。
 *
 *      将一个完全二叉树按层序排号依次存入数组:
 *          8
 *        /   \
 *       3	   9		---按层序排号存入数组---> [8,3,9,5,7,4,2]
 *      / \   / \
 *     5   7 4   2
 *
 *  5)在数组中,很容易得到下面的结论:
 *      1)下标为i的节点,其父节点的下标为(i-1)/2
 *      2)下标为i的节点,其左子节点的下标为2*i+1,右子节点的下标为2*i+2
 *
 * 6)使用场景:建立堆和调整堆的过程中会产生比较大的开销,故堆排序适用于排序元素较多的时候。eg:
 *      问题:top K
 *      方案:
 *          第1步)分治:先将所有的数据按照hash方法分解成多个较小数据集。
 *          第2步)使用堆排序分别找出这几个较小数据集中的topK。
 *          第3步)将第2步中各数据集的topk放在一个集合里,然后求出最终的topK。
 *
 * 7)复杂度:
 *      1)时间复杂度:最好、最坏、平均 的复杂度都是 O(nlogn),故堆排序堆输入的数据不敏感。
 *          建堆:O(n)
 *          调整:O(logn)
 *          总的时间复杂度 = 建堆(initHeap) + 排序(sortHeap) = O(n) + O(nlogn) = O(nlogn + n) = O(nlogn)
 *      2)空间复杂度:堆排序是就地排序,故其空间复杂度为O(1)。
 *
 * 8)稳定性:排序前后相同元素间的相对位置可能会发生改变,故堆排序是一种不稳定的排序算法。
 *
 */
public class HeapSort {

    public static int[] array;

    public static int adjustTime = 0;
    public static int whileTime = 0;

    public HeapSort(int[] array) {
        this.array = array;
    }

    /**
     * 根据子节点的索引来获取父节点的索引
     *
     * @param child
     * @return
     */
    public static int parentIndex(int child) {
        return (child - 1) / 2;
    }

    /**
     * 根据父节点的索引来获取左子节点的索引
     *
     * @param parent
     * @return
     */
    public static int leftChildIndex(int parent) {
        return parent * 2 + 1;
    }

    /**
     * 第一步:将待排序的n个元素构造成一个大根堆。
     *
     * 时间复杂度:O(n)		注:建堆的时间复杂度的推导过程比较复杂,记住建堆的时间复杂度为O(n)即可。
     *
     * 将数组初始化为大根堆:从下(最后一个非叶子节点)往上(堆的根节点)循环遍历。
     *
     *  要点:遍历存在左子节点的父节点,从最后一个非叶子结点开始遍历。
     *  说明:
     *      1)完全二叉树是按层序排号存入数组的,故二叉树的最后一个节点(即:数组中索引值最大的元素)一定是叶子节点,故最后一个节点一定有父节点,且最后一个节点的父节点就是 堆最后一个非叶子节点。
     *      2)二叉树的最后一个节点的索引为array.length-1,则其父节点(即:最后一个非叶子节点)的索引为(array.length-1-1)/2,故我们从array.length/2-1开始遍历!
     */
    public static void initHeap() {

        // 从下往上的循环
        for (int parentIndex = parentIndex(array.length-1); parentIndex >= 0; parentIndex--) {
            adjustHeap(array, parentIndex, array.length - 1);
        }
    }

    /**
     * 对堆进行排序
     * 
     * 时间复杂度:O(nlogn)
     */
    public static void sortHeap() {

        // array.length-1次 调整完成排序
        for (int i = array.length - 1; i > 0; i--) {

            // 第二步:将堆顶元素(数组中第一个元素)和当前未排序子序列中的最后一个元素交换
            swap(array, 0, i);

            // 第三步:交换后,将剩余的n-1个元素重新构造成一个大根堆
            adjustHeap(array, 0, i-1);
        }
    }

    /**
     * 调整堆:从上往下循环遍历,即 沿父节点的较大子节点向下调整
     *
     * 时间复杂度:O(logn)
     *
     * @param array
     * @param parentIndex
     * @param maxIndex
     */
    public static void adjustHeap(int[] array, int parentIndex, int maxIndex) { adjustTime++;

        int temp = array[parentIndex];              // 父节点的值
        int child = leftChildIndex(parentIndex);    // 左子节点的索引

        // 从上往下循环:沿父节点的较大子节点向下调整
        while (child <= maxIndex) {   // 左子节点必须在未排序子序列中

            whileTime++;    // 记录循环的次数,测试用。

            // 若当前节点(即:父节点)存在右子节点(且右子节点在未排序的子序列中),并且右子节点的值大于左子节点时,将右子节点的索引赋值给child
            if (child + 1 <= maxIndex && array[child + 1] > array[child]) {
                child++;   // 将左子节点转换为右子节点
            }

            // 此时,child表示 子节点中值最大的那个节点 的索引

            // 若当前节点(即:父节点)的值大于子节点的值时,直接退出。
            if (temp > array[child]) {
                break;
            } else {
                array[parentIndex] = array[child]; // 将子节点的值赋值给父节点

                // 选取子节点的左子节点继续向下调整(执行wile循环)
                parentIndex = child;
                child = leftChildIndex(parentIndex);
            }
        }
        // 若发生了交换,则parentIndex表示子节点的索引;若没有发生交换,则parentIndex仍旧表示父节点的索引。
        array[parentIndex] = temp;
    }


    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    public static void main(String[] args) {

        int[] array = {2, 13, 5, 7, 14, 6, 10, 8, 11, 4, 3, 9, 1, 12, 0};
        HeapSort heapSort = new HeapSort(array);
        heapSort.initHeap();
        heapSort.sortHeap();
        System.out.println("排序后数组" + Arrays.toString(heapSort.array) + " 调整次数" + adjustTime + " while循环次数" + whileTime);
    }

}
		
		
			

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值