堆排序

堆排序

堆排序是利用最大堆或者最小堆这种数据结构设计的排序算法。本文只介绍最大堆,最小堆的原理是相同的。

最大堆

堆排序算法中一般使用二叉堆,最大堆中的子节点值小于父节点的值,因此最大堆的根节点值最大。通过不断的将根节点值移到堆的末尾,并且调整堆使其满足最大堆的特性,达到排序的目的。

数组可以用来表示一个完全二叉树,对于数组索引i的节点,其两个孩子节点(若有)的索引分别为2xi+1、2xi+2。因此通过数组可以非常方便地维护一个最大堆。

将输入数组构建成最大堆

举例说明如何将输入的待排序数组构建成最大堆,假如我们有一个输入数组arr:

{4,8,5,3,13,11,9,2,7,1}

这个数组的完全二叉树表示如图1:

这里写图片描述

图1 输入数组的完全二叉树表示

从堆中最后一个非叶子节点开始调整堆,这里可以看出调整堆是自底向顶调整的,当调整到某个节点A的时候,该节点的孩子节点对应的树都已经调整成最大堆了。

对于图1来说,首先调整的是节点13。因为我们需要调整成最大堆,对于根节点13的子树来说,13的节点值大于它的子树节点值,已经符合最大堆的条件,因此该子树不需要调整。

接下来调整节点3,它有两个孩子,左孩子的值为2,右孩子的值为7,将最大的值调整到该子树的根节点,调整后的根节点为7,左孩子为2,右孩子为3,参考图2所示。

这里写图片描述

图2

接着调整节点5,它有两个孩子,左孩子的值为11,右孩子的值为9,该子树调整后的根节点为11,左孩子为5,右孩子为9,参考图3所示。

这里写图片描述

图3

接着调整节点8,它的左孩子为7,右孩子为13,这三个节点最大值为13,调整后的根节点为13,左孩子为7,右孩子为8。注意,这次调整后的左孩子没有变化,仍然是7,但是右孩子有变化,变为8,且以右孩子节点为根节点的子树还有子树,这次调整可能会破坏节点8为根节点的子树最大堆特性,因此需要重新调整(递归调整)。本次调整后的最大堆参考图4。

这里写图片描述

图4

接着调整节点4,它的左孩子为13,右孩子为11,最大值为13,调整后的根节点为13,左孩子为4,右孩子为11。注意,这样调整后右孩子没有变化,但是左孩子变为4,这会破坏左子树的最大堆特性,需要重新调整左子树使其满足最大堆特性。本次调整后的最大堆参考图5。

这里写图片描述

图5

到此为止,原输入数组已经构建成了最大堆,最大堆的根节点为13。

排序

怎么通过最大堆来将输入数组排序呢?最大堆构建完成后我们将堆顶元素和堆的最后一个节点元素交换,这样堆的最后一个节点就是堆的最大值,同时将堆的大小减1,并继续调整二叉树使其满足最大堆的特性。重复此步骤,最后的输入数组就是排序的了。

将图5中的根节点和堆的末尾节点交换,参考图6。

这里写图片描述

图6

节点13处已经是有序的了,不需要再调整,根节点为1的树此时显然不符合最大堆的特性,需要重新调整(不包括节点13)。调整后的最大堆参考图7。

这里写图片描述

图7

继续将最大堆的根节点11和堆的末尾(节点3)交换,调整后的堆参考图8。

这里写图片描述

图8

继续将最大堆的根节点9和堆的末尾节点2交换,调整后的堆参考图9。

这里写图片描述

图9

继续将最大堆根节点8和末尾节点1交换,调整后的堆参考图10。

这里写图片描述

图10

重复此步骤,直到堆调整完为止,最终的二叉树参考图11。

这里写图片描述

图11

到此为止,将原来输入数组通过最大堆完成排序了,图11从上往下,从左往右,遍历结果为

1, 2, 3, 4, 5, 7, 8, 9, 11, 13

已经是排序的。

代码实现

通过上面的说明,已经对堆排序的原来有了详细理解,接下来看下代码实现:

/**
 * <p>文件描述: 堆排序</p>
 *
 * @Author luanmousheng
 * @Date 17/8/1 下午3:46
*/
public class HeapSort {

    /**
     * 调整数组arr位置k处的堆,将位置k和子节点进行比较,将较大的值放在位置k处,重新调整子树为最大堆
     * @param arr 需要调整的数组
     * @param k 需要调整的节点索引
     * @param size 堆的大小
     */
    private static void adjustMaxHeap(int[] arr, int k, int size) {
        int leftChild = (k << 1) + 1;
        int rightChild = (k << 1) + 2;
        if (leftChild < size) {
            int max = k;
            if (arr[leftChild] > arr[max]) {
                max = leftChild;
            }
            if (rightChild < size && arr[rightChild] > arr[max]) {
                max = rightChild;
            }
            if (max != k) {
                //改变了左孩子或者右孩子,递归调整
                int tmp = arr[max];
                arr[max] = arr[k];
                arr[k] = tmp;
                adjustMaxHeap(arr, max, size);
            }
        }
    }

    /**
     * 创建最大堆,从底往上调整
     * @param arr
     */
    private static void buildMaxHeap(int[] arr) {
        //从最后一个非叶子节点开始调整
        for (int i = arr.length/2-1; i >= 0; i--) {
            adjustMaxHeap(arr, i, arr.length);
        }
    }

    /**
     * 排序前首先构建最大堆,然后将堆顶和最后一个位置的数据交换,重新调整位置0处的堆
     * @param arr
     */
    private static void sort(int[] arr) {
        buildMaxHeap(arr);
        for (int i = arr.length - 1; i > 0; i--) {
            int tmp = arr[i];
            arr[i] = arr[0];
            arr[0] = tmp;
            //调整堆的时候,去掉最后一项,size应减1
            adjustMaxHeap(arr, 0, i);
        }
    }

    public static void main(String[] args) {
        int[] arr = {4,8,5,3,13,11,9,2,7,1};
        sort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + ", ");
        }
    }
}

复杂度

时间复杂度:O(nlogn)

空间复杂度:O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值