目录
堆排序就是不断抽离堆顶元素(剩余队列中最大者),继续构造大顶堆
堆排序核心在于构造堆,本文以构造大顶堆为例
堆
说白了就是完全二叉树,
除最后一层外,其余各层都是满的,每层数量为(2^(h-1))
最后一层必须从左到右依次放置不能留空
构造大顶堆
大顶堆,就是每个父节点都比子节点要大,即任何子树都是大顶堆
比如给你数组,12345
遍历所有非叶子节点都与其孩子进行比较,子节点大于父节点时,进行替换
遍历可以从下到上,也可以从上到下。
本文采用从下到上,从右到左的方式遍历,数组指针向左移动即可,每次遍历比较确保比较后的所有子树都是大顶堆。
第一种方式
第二种方式
在每次调整堆时,非叶子节点都与其孩子进行比较,子节点大于父节点时,将该非叶子节点暂存到temp,原位置为代替换区,将子节点中的大者填充至i位置,i位置移至1,i的子节点中的大者4填充至i的位置,i位置移至大的子节点4的位置,当该节点为叶子节点,把temp值放入。结果同方式一。替换的过程有点像快排。
显然第二种方式来的简单,因为减少了交换。
堆排序就是不断抽离堆顶元素(剩余队列中最大者),继续构造大顶堆
具体操作就是不断让堆顶元素与堆底元素(该堆底位置不参与下次堆排序,所以就是抽离)互换,继续构造大顶堆
编写具体代码时,思考
非叶子节点有多少个,为什么是N(数组长度)/2
N(数组长度)=N(节点数)=n0(度为0的节点)+n1(度为1的节点)+n2(度为2的节点)= 根节点 + 所有孩子节点 = 1 + n1(度为1的节点的孩子)+ 2n2(度为2的节点的孩子)
所以 n2 + 1 = n0
非叶子节点即求n1+n2为多少
因为n1的值非1即0
那么用假设法
当 n1 = 1 时, 1 + n1 + 2n2 = N =====> 2(n1+n2) = N
当 n1 = 0时,1+ n1+ 2n2 = N =====> 1+2(n1+n2) = N ======> n1 + n2 = (N-1) /2 因为n1为0时,n为奇数,
又因为奇数整型除法的特殊性 N /2 = (N-1)/2
所以非叶子节点有 N / 2个
代码
public class HeapSort {
public static void main(String []args){
int[] arr = new int[]{3,44,38,5,47,15,36,26};
sort(arr);
}
public static void sort(int []arr){
//1.构建大顶堆
for(int i=arr.length/2-1;i>=0;i--){
//因为是完全二叉树,所以最后一个非叶子节点之前的元素都是非叶子结点
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,i,arr.length);
SortUtil.printArray(arr);
}
System.out.println("part2");
//2.调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//将堆顶元素与末尾元素进行交换
adjustHeap(arr,0,j);//重新对堆进行调整
SortUtil.printArray(arr);
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int []arr,int i,int length){
int temp = arr[i];//先取出当前元素i
for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
k++;
}
if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//将temp值放到最终的位置
}
public static void swap(int []arr,int a ,int b){
int temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}