对堆排序有疑虑?只需五步,彻底搞定堆排序:
在说堆排序之前,首先应该明确一些概念:
一.概念:
1.完全二叉树:
a.从作为第一层的跟开始,除了最后一层外,第N层的元素的个数,都必须是2的N次方.
b.最后一行的元素,必须从左边开始放,且中间不能有空格.
说明:满足这两个特点的二叉树就是完全二叉树.
2.最大堆:
在满足完全二叉树的条件下,每个父节点的值都大于其子结点的值,一般最大堆用于升序排序.
3.最小堆:
在满足完全二叉树的条件下,每个父节点的值都小于其子节点的值,一般最小堆用于降序排序.
接下来以提问题方式让你搞懂堆排序:
二.总体思路
把数组中所有的数想象成完全二叉树中的结点,对这个完全二叉树构建最大堆,对数组而言也就是寻找最大值,找到最大值后把这个最大值放在最后,然后再对剩下的数再次构建最大堆(寻找第二大的值),如此重复进行即可
三.具体步骤.(总共需要五大步)
1.构建二叉树(这一步理解就好,并不需要代码实现):
-
问题一: 其实真的需要构建一个二叉树吗?
不需要,在每个数组一确定之后,对应的二叉树就已经构建好了,这个二叉树只是人 们想象出来的, 便于进行排序思考的模型.只不过这个模型需要遵循一定的规则.
-
问题二:那么需要遵循哪些规则呢?
-
其实就是二叉树中的父节点和子节点在数组中的对应关系. 规定:父节点如果是第i个,左子节点就是2i+1,右子节点就是2i+2.
2.排序1(就是构建最大堆的过程,为什么构建最大堆? 因为要找到数组中的最大值)
从下往上,从第一个非叶子节点(有子节点)的节点开始,如果该结点大于它的子节点, 则无需交
换,否则与最大子节点交换.
-
问题一:那么对应数组中怎么找第一个非叶子结点呢?
i=arr.length/2-1,这个i就是,那么这个i前面就一定是非叶子节点,所以寻找下一个非叶子 结点直接i-1即可.
-
问题二:那么为什么i=arr.length/2-1?
-
这个是根据数学推出来的,我们知道数组中最后一个数一定是最后一个非叶子结点通过2i+1或2i+2算出来的. 也就是: 2i+1=arr.length-1. 2i+2=arr.length-1. 就是一个中学数学题,求i.发现两个式子都可以求出i=arr.length/2-1.(因为3/2和2/2的结果是一样的,都是1)
*注意:对于整体来说,排序是一个自下而上的过程,而对于单个结点来说,排序又是一个自上而下的过程.
在这顺便可以看一下堆和数组的对应关系
这里3和7交换后,还需要再往下比较,还需要再和4交换.
3,交换.
满足最大堆特点之后,其实就是找到了数组中的最大值.而且放在数组的第0位.
我们现在要做的当然就是把最大值放到最后面去
(对应二叉树中也就 是把跟结点和最后一行的最右边结点进行交换).**
4.排序2.
这里和排序1稍有区别,因为我们已经排好了最大值,所以不需要最大值参与进来了.
也就是除了最大值以外,其余所有的数再进行排序,
构建最大堆.目的就是为了寻找第二个最大值.
5.然后不断重复3和4,直到所有的都排好序即可.
最后附上整个程序:
public static void heapSort(int[] nums){
if(nums==null||nums.length<2) return;
int len=nums.length/2-1;
//从第一个非叶子节点开始构建最大堆
for(int i=len;i>=0;i--){
sort(nums,i,nums.length-1);
}
for(int i=nums.length-1;i>=0;i--){
//最大堆构建之后,开始依次取最大值(一定放在第0个位置),放到元素末尾
swap(nums,0,i);
//对除去最大值之外的数组重新按最大堆模型进行排序
sort(nums,0,i-1);
}
}
/**
* 进行最大堆的构建,也就是排序
* @param nums 排序数组
* @param index 从哪一个节点开始排序
* @param len 排序的范围(包括叶子节点的范围)
*/
public static void sort(int[] nums, int index, int len){
for(int i=index;i<=len;){
int max=i;
//下面是判断最后节点是否有左右子节点,以免越界,因为在第二次排序时最大堆的范围是依次减小的
if((2*i+2)<=len){
max=nums[2*i+1]>nums[2*i+2] ? 2*i+1 : 2*i+2;
}else if((2*i+1)<=len)
max=2*i+1;
//当前节点小于左右子节点的最大值,交换
if(nums[i]<nums[max]){
swap(nums,i,max);
i=max;
}else
break;
}
}
public static void swap(int[] nums, int left, int right){
int help=nums[left];
nums[left]=nums[right];
nums[right]=help;
}
测试:[1,3,6,9,10,2]
输出结果: