版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
对堆排序有疑虑?只需五步,彻底搞定堆排序:
在说堆排序之前,首先应该明确一些概念:
一.概念:
1.完全二叉树:
a.从作为第一层的跟开始,除了最后一层外,第N层的元素的个数,都必须是2的N次方.
b.最后一行的元素,必须从左边开始放,且中间不能有空格.
说明:满足这两个特点的二叉树就是完全二叉树.
2.最大堆:
在满足完全二叉树的条件下,每个父节点的值都大于其子结点的值,一般最大堆用于升序排序.
3.最小堆:
在满足完全二叉树的条件下,每个父节点的值都小于其子节点的值,一般最小堆用于降序排序.
接下来以提问题方式让你搞懂堆排序:
二.总体思路
把数组中所有的数想象成完全二叉树中的结点,对这个完全二叉树构建最大堆,对数组而言也就是寻找最大值,找到最大值后把这个最大值放在最后,然后再对剩下的数再次构建最大堆(寻找第二大的值),如此重复进行即可
三.具体步骤.(总共需要五大步)
1.构建二叉树(这一步理解就好,并不需要代码实现):
-
问题一: 其实真的需要构建一个二叉树吗?
不需要,在每个数组一确定之后,对应的二叉树就已经构建好了,这个二叉树只是人 们想象出来的, 便于进行排序思考的模型.只不过这个模型需要遵循一定的规则.
-
问题二:那么需要遵循哪些规则呢?
-
其实就是二叉树中的父节点和子节点在数组中的对应关系. 规定:父节点如果是第i个,左子节点就是2i+1,右子节点就是2i+2.
2.排序1(就是构建最大堆的过程,为什么构建最大堆? 因为要找到数组中的最大值)
从下往上,从第一个非叶子节点(有子节点)的节点开始,如果该结点大于它的子节点, 则无需交
换,否则与最大子节点交换.
-
问题一:那么对应数组中怎么找第一个非叶子结点呢?
i=arr.length/2-1,这个i就是,那么这个i前面就一定是非叶子节点,所以寻找下一个非叶子 结点直接n-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 class HeapSort {
public static int[] HeapSort(int[] arr){
if(arr==null||arr.length<1){
throw new RuntimeException("Invalid Parament");
}
//构建最大堆,for实现从下而上的思路.里面的sort排序实现对单个结点从上而下
for (int i = arr.length/2-1; i >=0 ; i--) {
sort(arr,i,arr.length);
}
//开始交换,这里是对最后一个点的循环,每次把最大值移至最后,把前面的进行排序,重新构建最大堆
for (int i = arr.length-1; i >0 ; i--) {
//交换
swap(arr,i,0);
//对除去最大值之外的数组重新按最大堆模型进行排序
//注意这里排序的范围是逐渐减小的
sort(arr,0,i);
}
return arr;
}
/**
* 进行最大堆的构建,也就是排序
* @param arr 要排序的数组
* @param i 从哪一个结点开始比较,排序
* @param length 排序的范围,也就是排序到哪一个结点的范围
*/
private static void sort(int[] arr, int i,int length) {
int max;//指向两个子节点中大的结点
//比较两个子节点,给出最大值
//这个循环是对父节点的i的循环.2i+2<=length-1.从而对父节点做出限制.
for (int node= i; node < length/2-1; ) {
//这里如果最大值排在最后,i不变,还是会把最大值参与进来,所以要用length进行限制.
if((2*node+2)<length){
max=arr[2*node+1]>arr[2*node+2] ? 2*node+1 : 2+node+2;
}else{
max=2*node+1;
}
//如果父节点小于子节点中的最大值,交换
if(arr[node]<arr[max]){
swap(arr,node,max);
//此时max指向交换后的子结点,需要继续向下比较,所以要让i指向交换后的子节点.
node=max;
}else {
//如果父节点大,直接退出循环
break;
}
}
}
private static void swap(int[] arr, int i, int max) {
int flag=arr[i];
arr[i]=arr[max];
arr[max]=flag;
}
//测试
public static void main(String[] args) {
int[] arr={3,5,2,3,1};
int[] ints = HeapSort(arr);
for (int anInt : ints) {
System.out.println(anInt);
}
}
}