堆排序有最大堆,最小堆。
最大堆:非叶子节点比左右子节点要大
最小堆:非叶子节点比左右子节点要小
以构建最大堆为例,堆排序的过程:
1、原始数组形成一个顺序堆。数组中下标索引为i的节点,左节点是i2 +1,右节点是i2+2
2、初始化堆,从最后一个叶子节点的父节点开始一层层向上遍历,使得每一对父子节点中的最大节点上浮,维持最大堆的性质。
如果有交换位置的操作,那么要以交换后的新子节点为父节点递归遍历,以维持该分支上的最大堆性质。
直到遍历到根节点,此时根节点最大。
3、排序阶段:将根节点与最后一个叶子节点交换位置,交换过位置的尾部叶子节点就是从小到大的排序,最后的叶子节点的索引相对应也减1。
然后以根节点,维护最大堆性质,同样的,如果有交换位置的操作,那么要以被交换的子节点为父节点递归遍历,以维持该分支上的最大堆性质
个人认为,堆排序的核心点在于:如果有交换位置的操作,那么要以交换后的新子节点为父节点递归遍历,以维持该分支上的最大堆性质
以下是java代码:
public static void main(String[] args){
Integer[] arr = new Integer[]{23,123,234,1,3,432,67,3,56,24,23,7876,124,12,898,854,85,98,2,879};
heapSort(arr);
for(Integer i:arr){
System.out.println(i);
}
}
/**
* 构建最大堆,数组最终结果由小到大排列
* 形成堆之后,数组中下标索引为i的节点,左节点是i*2 +1,右节点是i*2+2
* @param arr
*/
public static void heapSort(Integer[] arr){
initHeap(arr);
//正式排序
for(int i=arr.length-1;i>=0;i--){
exchange(arr,0,i);//根节点与尾部节点交换,尾部节点组成由小到大的有序数组
//交换后,维护以根节点为父节点的最大堆。
keepMaxHeap(arr,0,i);//最后的叶子节点的上限值也相应发生变化
}
}
/**
* 初始化堆,形成一个最大堆
* @param arr
*/
public static void initHeap(Integer[] arr){
int i = 0;
if(arr.length%2==0){
i=(arr.length-2)/2;
}else{
i=(arr.length-3)/2;
}
// 从最后一个叶子节点的父节点开始一层层向上遍历
for(;i>=0;i--){
keepMaxHeap(arr,i,arr.length);
}
}
/**
* 维护以index为父节点的最大堆
* 较大节点上浮
* @param arr
* @param index 需要操作的父节点的下标索引
* @param endIndex 子节点不可超过的最大下标索引,index<endIndex
*/
public static void keepMaxHeap(Integer[] arr, int index, int endIndex){
if(arr ==null || index>endIndex){
return;
}
int parentIndex = index;
int leftIndex = parentIndex*2 +1;
int maxNodeIndex = parentIndex;
if(leftIndex<endIndex && arr[maxNodeIndex] < arr[leftIndex]){
//左节点比父节点大
maxNodeIndex = leftIndex;
}
int rightIndex = leftIndex +1;
if(rightIndex<endIndex && arr[maxNodeIndex] <arr[rightIndex]){
//右节点比左,父节点中最大的还大
maxNodeIndex = rightIndex;
}
if(maxNodeIndex!=parentIndex){
//说明父节点不是最大的,需要交换位置
exchange(arr,parentIndex,maxNodeIndex);
//交换位置后要继续向下递归,维持最大堆的性质
keepMaxHeap(arr,maxNodeIndex,endIndex);
}
}
public static void exchange(Integer[] arr,int i,int m){
Integer temp = arr[i];
arr[i]=arr[m];
arr[m]=temp;
}