今晚,咱们就聊聊堆排序吧!

作者:小灰

来自:程序员小灰(ID:chengxuyuanxiaohui)


在上一篇漫画中,小灰介绍了 二叉堆 这样一种强大的数据结构:


漫画:什么是二叉堆?(修正版)


那么,这个二叉堆怎样来使用呢?我们这一期将会详细讲述。


640?wx_fmt=jpeg



640?wx_fmt=jpeg



让我们回顾一下二叉堆和最大堆的特性:


1.二叉堆本质上是一种完全二叉树

2.最大堆的堆顶是整个堆中的最大元素


当我们删除一个最大堆的堆顶(并不是完全删除,而是替换到最后面),经过自我调节,第二大的元素就会被交换上来,成为最大堆的新堆顶。


640?wx_fmt=png



正如上图所示,当我们删除值为10的堆顶节点,经过调节,值为9的新节点就会顶替上来;当我们删除值为9的堆顶节点,经过调节,值为8的新节点就会顶替上来.......


由于二叉堆的这个特性,我们每一次删除旧堆顶,调整后的新堆顶都是大小仅次于旧堆顶的节点。那么我们只要反复删除堆顶,反复调节二叉堆,所得到的集合就成为了一个有序集合,过程如下:



删除节点9,节点8成为新堆顶:


640?wx_fmt=png



删除节点8,节点7成为新堆顶:


640?wx_fmt=png



删除节点7,节点6成为新堆顶:


640?wx_fmt=png



删除节点6,节点5成为新堆顶:


640?wx_fmt=png



删除节点5,节点4成为新堆顶:


640?wx_fmt=png



删除节点4,节点3成为新堆顶:


640?wx_fmt=png



删除节点3,节点2成为新堆顶:


640?wx_fmt=png



到此为止,我们原本的最大堆已经变成了一个从小到大的有序集合。之前说过二叉堆实际存储在数组当中,数组中的元素排列如下:


640?wx_fmt=png



由此,我们可以归纳出堆排序算法的步骤:


1. 把无序数组构建成二叉堆。

2. 循环删除堆顶元素,移到集合尾部,调节堆产生新的堆顶。



640?wx_fmt=jpeg



640?wx_fmt=jpeg



public class HeapSort {

 
 
  1. /**

  2. * 下沉调整

  3. * @param array     待调整的堆

  4. * @param parentIndex    要下沉的父节点

  5. * @param parentIndex    堆的有效大小

  6. */

  7. public static void downAdjust(int[] array, int parentIndex, int length) {

  8.    // temp保存父节点值,用于最后的赋值

  9.    int temp = array[parentIndex];

  10.    int childIndex = 2 * parentIndex + 1;

  11.    while (childIndex < length) {

  12.        // 如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子

  13.        if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex]) {

  14.            childIndex++;

  15.        }

  16.        // 如果父节点小于任何一个孩子的值,直接跳出

  17.        if (temp >= array[childIndex])

  18.            break;

  19.        //无需真正交换,单向赋值即可

  20.        array[parentIndex] = array[childIndex];

  21.        parentIndex = childIndex;

  22.        childIndex = 2 * childIndex + 1;

  23.    }

  24.    array[parentIndex] = temp;

  25. }


  26. /**

  27. * 堆排序

  28. * @param array     待调整的堆

  29. */

  30. public static void heapSort(int[] array) {

  31.    // 1.把无序数组构建成二叉堆。

       for (int i = (array.length-2)/
    2; i >= 0; i--) {

  32.        downAdjust(array, i, array.length);

  33.    }

  34.    System.out.println(Arrays.toString(array));

  35.    // 2.循环删除堆顶元素,移到集合尾部,调节堆产生新的堆顶。

  36.    for (int i = array.length - 1; i > 0; i--) {

  37.        // 最后一个元素和第一元素进行交换

  38.        int temp = array[i];

  39.        array[i] = array[0];

  40.        array[0] = temp;

  41.        // 下沉调整最大堆

  42.        downAdjust(array, 0, i);

  43.    }

  44. }


  45. public static void main(String[] args) {

  46.    int[] arr = new int[] {1,3,2,6,5,7,8,9,10,0};

  47.    heapSort(arr);

  48.    System.out.println(Arrays.toString(arr));

  49. }

}


640?wx_fmt=jpeg



640?wx_fmt=jpeg



二叉堆的节点下沉调整(downAdjust 方法)是堆排序算法的基础,这个调节操作本身的时间复杂度是多少呢?


假设二叉堆总共有n个元素,那么下沉调整的最坏时间复杂度就等同于二叉堆的高度,也就是O(logn)


我们再来回顾一下堆排序算法的步骤:


1. 把无序数组构建成二叉堆。

2. 循环删除堆顶元素,移到集合尾部,调节堆产生新的堆顶。


第一步,把无序数组构建成二叉堆,需要进行n/2次循环。每次循环调用一次 downAdjust 方法,所以第一步的计算规模是  n/2 * logn,时间复杂度 O(nlogn)


第二步,需要进行n-1次循环。每次循环调用一次 downAdjust 方法,所以第二步的计算规模是 (n-1) * logn ,时间复杂度 O(nlogn)


两个步骤是并列关系,所以整体的时间复杂度同样是 O(nlogn)



640?wx_fmt=jpeg



640?wx_fmt=jpeg



640?wx_fmt=jpeg



640?wx_fmt=jpeg



640?wx_fmt=jpeg


—————END—————

长按订阅同类文章

一起搬砖的点个赞

640?wx_fmt=jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值