今天扫完厕所没事干,四处闲逛,看到了办公室小伙子的学习笔记,发现他在头疼一个叫做堆排序(MaxHeap)的东西,还画了桩二叉木头在旁边,老夫略感兴趣,便偷偷拍下笔记回杂物间研究一番。
何为堆,老头我的工作经验里就只有每天所面对的垃圾堆,先丢重的再丢轻的也是我多年来的习惯,看来这堆排序也是同理,大的或者小的排前,后人来了也好处理。好了,也不多废话了,上纸笔。
今天打扫公司10层楼收了这么多斤垃圾,使用先大后小的顺序来清运的话。
| 12 | 63 | 58 | 95 | 41 | 35 | 65 | 0 | 38 | 44 |
-
先摆成二叉木头的格式。
-
三两成堆,不如从最底下*那堆开始处理
- 每次都把最重的那堆放到树根,顺序为自底向上一堆一堆进行处理。具体操作为:在我们传入待排序数组(arr)、起点下标(i)、终点下标后(lens-1)后,其实就自底向上寻找子树最大节点的过程:
-
微观情况下,是在倒数第二层的小子树(n=3)找最大值与父节点交换。
-
宏观情况下,是在整棵树中找最大值放在根节点。
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; //子变父,继续进入循环
}
}
}
- 这个过程是对无序数组进行非线性的操作,因此理解起来颇具难度,结合流程图和代码会有助于加深印象。
-
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为本数组的最大值。
-
由此可见,如此费劲交换下来只拿到了一个数组的最大值,顺位下来的第二大,第三大,第四大位置在哪我们也不得而知, 而且最后我们排序结束的结果不能是棵二叉树,得是有序数组啊!
所以依据层次遍历的思想,在完成二叉排序树的建立后,我们将根节点放到最后一个位置,并将数组规模(n)缩小,只排前i=n-1个节点,此循环依次递减,直到i=1。
希望本文能对你有所帮助,也欢迎在评论交流,如有错误还请大佬不吝赐教,回去接着扫地了。
封面来源 @原神 |© 米哈游版权所有。