[HeapSort]阿珍都能看懂的“堆排序”

90 阅读4分钟

今天扫完厕所没事干,四处闲逛,看到了办公室小伙子的学习笔记,发现他在头疼一个叫做堆排序(MaxHeap)的东西,还画了桩二叉木头在旁边,老夫略感兴趣,便偷偷拍下笔记回杂物间研究一番。

何为堆,老头我的工作经验里就只有每天所面对的垃圾堆,先丢重的再丢轻的也是我多年来的习惯,看来这堆排序也是同理,大的或者小的排前,后人来了也好处理。好了,也不多废话了,上纸笔。

今天打扫公司10层楼收了这么多斤垃圾,使用先大后小的顺序来清运的话。

              | 12 | 63 | 58 | 95 | 41 | 35 | 65 | 0 | 38 | 44 |
  1. 先摆成二叉木头的格式。

    image.png

  2. 三两成堆,不如从最底下*那堆开始处理

image.png

(*这是因为堆是符合完全二叉树的特性。按层次放入的话,左孩子节点下标为父节点的2倍+1,所以可以推断出结点树为n的话,最底层节点的父节点下标刚好就是(n/2)-1),而且往前看下标[0]~[n/2-1]全是非叶子节点!
  1. 每次都把最重的那堆放到树根,顺序为自底向上一堆一堆进行处理。具体操作为:在我们传入待排序数组(arr)、起点下标(i)、终点下标后(lens-1)后,其实就自底向上寻找子树最大节点的过程:
  • 微观情况下,是在倒数第二层的小子树(n=3)找最大值与父节点交换。

  • 宏观情况下,是在整棵树中找最大值放在根节点。

    image.png

    for(int i= len/2-1;i>=0;i--){   
        MaxHeapify(arr,i,len-1);     //[len/2]~[0]从最后一个子树往上调整
    }

因此调整完倒数第二层的所有子树后,子树均是符合父节点大于左右子树节点。此时操作空间来到了倒数第三层,先前子树的根节点成为了此时操作空间的子树,但先前子树我们的大小对比参照不同,因此我们需要判断2个东西来确保大小顺序的一致性:

  • 目前的树中,有无右子树节点,右子树节点是否比左子树大。
  • 目前父节点是否大于子树节点。
//MaxHeapify(arr,start,end)

while(lchild<=end){        //孩子节点仍在范围时 继续
        if(lchild + 1<= end && arr[lchild]<arr[lchild+1]){ 
            ++lchild;     //切换到右子树,因为要找子节点中的最大值跟父节点交换
        }
        if(arr[lchild]<arr[parent]){
            return;      //如果父节点一直大于孩子节点,返回
        }
        else{
            swap(arr[parent],arr[lchild]);
            parent = lchild;
            lchild = 2*parent+1;     //子变父,继续进入循环

        }
    }
}
  1. 这个过程是对无序数组进行非线性的操作,因此理解起来颇具难度,结合流程图和代码会有助于加深印象。

如图这是最大元素95一步一步交换到根节点的一部分过程,我们以Parent的初始值作为监视对象,过一遍MaxHeapify的过程。
  • parent = 4,41没有右子树,44>41,因此交换了44和41,parent = 9,child值越界,循环结束。

  • parent = 3,95有右子树,且比左子树大,lchild++切换到右子树的位置,但95 [parent]>38 [lchild++],循环结束。

  • parent = 2,58有右子树,且比左子树大,但同理65 [parent]>58 [lchild++],循环结束。

  • parent = 1,左子树已有序,略。

  • 而在parent = 0的这一趟中,12有左右子树,左子树(95)大于右子树(58)大于12 [parent],此时交换95和12,循环继续并且(12)parent = 1,(63)lchild = 3。交换12和63,(12)parent = 3,(0)lchild = 7,(38)lchild++=8,右子树大于左子树大于根节点,交换12和38。本趟结束,根结点95为本数组的最大值。

  1. 由此可见,如此费劲交换下来只拿到了一个数组的最大值,顺位下来的第二大,第三大,第四大位置在哪我们也不得而知, 而且最后我们排序结束的结果不能是棵二叉树,得是有序数组啊!

    所以依据层次遍历的思想,在完成二叉排序树的建立后,我们将根节点放到最后一个位置,并将数组规模(n)缩小,只排前i=n-1个节点,此循环依次递减,直到i=1。

如图分割后的序列再进行一遍MaxHeapify操作,就可以逐步将最大值放置到数组的尾部。 完整代码如下:

希望本文能对你有所帮助,也欢迎在评论交流,如有错误还请大佬不吝赐教,回去接着扫地了。

封面来源 @原神 |© 米哈游版权所有。