看了几篇博客,以自己的理解默写了一遍自己理解的堆排序,然后本地跑了一遍,可以正常排序,因为怕以后看不懂,注释写的很啰嗦
/**
* 堆排序
* 堆一般指一个完全二叉树,根据其特性,可以分为大根堆,小根堆
* 大根堆:任意父节点的值大于或等于2个孩子节点的值
* 小根堆:任意父节点的值小于或等于2个孩子节点的值
* 就是利用堆的这样的特性用来排序,所以叫做堆排序
* 比较常用的就是大根堆了,因为根结点是堆的最大节点,所以可以类似于冒泡,选择排序那样,依次得到最大节点,次最大节点,完成一个序列的排序
* 每次都从根结点把堆的最大值交换到堆低,然后调整堆为新的大根堆,不过这时候堆的长度得减1,依次下去就可以完成排序
* 小根堆也一样,可以不断选择最小值,然后不断调整小根堆,获得次最小,然后也是一个排好序的序列
* 一颗完全二叉树,其层次遍历的结果可以顺序存在一个数组里
* 其父节点i的位置和左右孩子的位置关系是:左孩子:2*1+1;右孩子:2*1+2;节点的父节点位置:(i-1)/2
*
*/
public class HeapSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] array = {87,45,78,32,17,65,53,9,122,133};
heapSort(array);
for(int i : array){
System.out.print(i+" ");
}
}
/**
* 堆排序
* 使用大根堆的特性,每次获取堆顶的最大值,放到堆底,然后堆的长度减1,调整堆使其再次恢复大根堆特性,依次循环,直到只剩下一个节点为止
* @param data
* @return
*/
public static int[] heapSort(int[] data){
if(data == null || data.length <= 1) return data;
//构建大根堆
buildMaxHeap(data);
for(int i = data.length -1; i > 0; i--){//自减为0后,表示堆还剩一个节点没有排序
//交换堆顶、底的值
int temp = data[i];
data[i] = data[0];
data[0] = temp;//可能破坏了大根堆的特性,指定根结点进行调整]
adjustMaxHeap(data, 0, i);//此时已经确定了最大的节点到堆底,所以堆的长度得减1
}
return data;
}
/**
* 构建大根堆
* 思路:
* 从右向左,从下到上不断调根结点为大根堆,起始点为最后一个父节点:(data.length-1-1)/2,终点为
* 树的根结点 0
* @param data
* @return
*/
public static int[] buildMaxHeap(int[] data){
if(data == null || data.length <= 1) return data;
for(int i = (data.length-1-1)/2; i >= 0; i--){
adjustMaxHeap(data, i, data.length);//构建大根堆时,长度一直不变
}
return data;
}
/**
* 调整大根堆,使其保持大根堆的特性,自上而下地向下调整,直到堆底
* @param data 需要调整的堆
* @param headIndex 需要调整的堆的根节点的位置
* @param length 需要调整的堆的长度
* @return
*/
public static void adjustMaxHeap(int[] data, int headIndex, int length){
//判断要调整的根结点的合法性,最后一个根结点的位置是(length-1-1)/2
if(headIndex <= (length-1-1)/2){
//位置合法,开始调整大根堆,判断其是否大于或等于左右孩子的最大值,是则不需要调整,直接跳出循环,否则与最大值交换,然后继续往下调整
//一直调整所需要比较和交换值的子节点到堆底结束,即可以执行到data.length -1位置的节点
//因为调整大根堆使,可能会不停的那较大的孩子节点的值覆盖父节点,所以需要保存当前父节点的值
int temp = data[headIndex];
for(int i = 2*headIndex+1; i < length; i = 2*i+1){//每次循环开始,即需要确认一个根结点headIndex是否需要调整,使其保持大根堆的性质
//取左右孩子最大值的位置
//i < data.length -1 这个条件的限制是为了确定i是左孩子的情况下,那么一定存在右孩子,如果i不满足条件,那么就不会有右孩子了
if(i < length -1 && data[i] < data[i+1]){
//i是左孩子,并且值比右孩子小,那么最大值的位置就是右孩子
i++;
}
//此时已经得到左右孩子的最大位置,判断是否需要调整堆
if(temp >= data[i]){
//根结点的值已经大于或等于左右节点,不需要调整堆
break;
}else{
//需要调整
data[headIndex] = data[i];//将最大的值赋值到根结点
//原headIndex的位置的值已经确定,这时候需要向下继续调整,因为我们还不知道 temp因该放在哪,即无论放在哪里都可能破坏
//大根堆的结构,所以需要依次向下调整,直到不需要调整为止,才能保证大根堆的特性
//现在temp可能会被放在刚刚赋值过去的i上,所以以i为根结点,继续调整大根堆
headIndex = i;//以headIndex为根结点,并且假设将temp的值也迁移到这个新的headIndex上(可以这么想,因为每次都是temp与左右孩子较大值取比较的)
//继续寻找左孩子的位置,即i = 2*i+1
}
}
//此时循环结束,可能是headIndex的值本来就不需要调整,
//或者进行调整后,因为把最大孩子节点的值赋值到根结点后,更新较大孩子节点的位置为headIndex,此时的headIndex的值不需要调整
//或者headIndex的位置已经超出最后一个父亲节点的位置
//那么headIndex就可以存放调节父节点的值temp了
data[headIndex] = temp; //指定父节点调整大根堆完成
}
}
}