这一篇介绍堆排序,堆排序需要具有背景知识二叉堆。
一、二叉堆(优先队列)
二叉堆是一种最常用的优先队列。
- 堆的性质:
1.1 结构性质:堆是一颗完全二叉树,注意完全二叉树指的是除最底层外其他都是完全被填满的,对底层元素从左到右依次填入(可以不填满);
1.2 堆序性质:以小堆为例,最小元素肯定在根上,而且每个子树也是一个小堆即任意节点的值小于其所有的后裔节点值。
- 完全二叉树的性质:
完全二叉树可以用一个数组直接表示,类似层序遍历的结果
对于数组中任一位置i上的元素,其父节点位置(i-1)/2,左子树位置:2i+1,右子树位置:2i+2
- 堆核心操作:
3.1 上滤:插入一个新元素,先放在最底部,然后一步一步的和其父节点判断交换,最后到达其正确的位置上去的过程;
3.2 下滤:一般根是最小的,是我们的目标,可是取走根之后留下一个空穴怎么办呢?取子节点值较小的放入空穴中,而且为保证每个子树也是小堆,移上去的节点所在子树也要下滤调节。
二、堆排序过程
基本思想:将待排数组看做一个完全二叉树,先建大堆(Build Heap),然后交换根元素和最后一个元素,再从根开始进行调节保持大堆特性,一直持续该步骤直到最后不需要交换为止。
时间复杂度:O(N+NlogN)=O(NlogN),(大O计法的话N可以省略)(最好、最坏和平均都是O(N*logN))
稳定性:不稳定
参考代码
//堆排序
void heapSort(int[] arr){
if(arr==null)throw new NullPointerException();
if(arr.length<=1)return;
//Build Heap
for(int i=arr.length/2;i>=0;--i){
percDown(arr,i,arr.length);
}
//Swap And PercDown
for(int i=arr.length-1;i>0;--i){
swap(arr,0,i);
percDown(arr,0,i);
}
}
/**
* 交换数组元素
*/
void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
/**
* 下滤操作
*/
void percDown(int[] arr,int i,int n){
int child;
int tmp;
for(tmp=arr[i];2*i+1<n;i=child){ //subtree also need percdown
child = 2*i+1;
if(child!=n-1 && arr[child+1]>arr[child])
child++;//find bigger value for swap
if(tmp<arr[child])
arr[i]=arr[child];
else
break;
}
arr[i] = tmp;
}