排序定义
堆排序是利用树的结构特性,构造大顶堆或者小顶堆,通过替换与调整在原有数组上进行排序。
实现形式
以递增排序为例,将待排序数组构造为大顶堆(不需额外的数据空间,在自身数组上进行操作,原地排序,),所谓大顶堆也就是非叶子节点的节点元素值比它的子节点元素值要大,按此规则构建树结构,则根节点即为最大值。也就是当前数组的第一个元素即为最大值(带有元素跳跃,类似选择排序,都为非稳定排序),将最大值与数组最后一个元素交换,则数组已经实现一个元素的排序;调整替换元素后的树为大顶堆,重复进行替换、调整操作,最后即可形成排序数组。
因为大顶堆为完全二叉树,以数组构造大顶堆不存在空间浪费,节点坐标关系如下(从零开始):
非叶子节点下标:k,左子节点下标:2*k+1,右子节点下标:2*k+2(如果存在的话),父节点下标:(k-1)/2(如果存在的话)
如果节点个数为 n,即当前待排序数组长度为 n,构建的大顶堆中,最后一个非叶子节点下标为:n/2-1
(因为数组长为n,则最后一个叶子下标:n-1,则最后一个非叶子节点下标即为:((n-1)-1)/2,也就是:n/2-1)
之所以提非叶子节点,是因为大顶堆要求父节点元素比子节点元素值大,所以在调整堆结构时,无须加入对叶子节点的判断,直接从最后一个非叶子节点开始向前构造大顶堆,以及排序调整操作截止到最后一个非叶子节点(需要加入对待排序数组长度的限制,免得侵入最后以及排好序的部分)
示例
初始带排序数组:2,1,5,7,3,4,6,有图有真相
最初不做处理对应的树结构:
最后一个非叶节点:5,调整
调整后的5为叶子节点,不需要循环向下调整,接着向前调整节点:1,调整
接着调整节点:2
调整后的节点2为非叶子节点,循环进行调整,调整
此时大顶堆构造完成,根节点即数组第一个元素为最大值,然后进行替换、调整的排序操作。
首先替换节点:5,7,并进行调整
替换节点:4,6,并进行调整
替换节点:2,5,并进行调整
如此不断重复替换、调整操作,直至待排序数组长度为1,整个数组即为递增排序的数组。
代码
public class t{
public static void main(String[] args){
int[] arr=new int[]{2,1,5,7,3,4,6};
heap h=new heap(arr);
h.show();
}
}
class heap{
private final int[] arr;//元素数组
public heap(int[] arr){
this.arr=arr;
init();//初始化
}
private void init(){//大顶堆的初始化
for(int i=arr.length/2-1;i>=0;i--){
update(i,arr.length);
}
big_heap();//排序
}
private void big_heap(){//排序
int len=0;
while(++len<arr.length){
int temp=arr[0];
arr[0]=arr[arr.length-len];
arr[arr.length-len]=temp;
update(0,arr.length-len);
}
}
private void update(int n,int len){//更新n节点子树为大顶堆
if(n<len/2){
int index=n;//index指向最大元素下标
if(arr[index]<arr[2*n+1]){
index=2*n+1;
}
if(2*n+2<len&&arr[index]<arr[2*n+2]){
index=2*n+2;
}
if(index!=n){
int temp=arr[n];
arr[n]=arr[index];
arr[index]=temp;
update(index,len);//如果发生调整,则递归调整直到不发生调整
}
}
}
public void show(){//观察数组顺序
for(int i:arr){
System.out.print(i+" ");
}
}
}
总结
利用树的结构特性构建二叉树进行原地排序,由树的结构知每次调整树最坏复杂度为O(log n)(红黑树调整结构复杂度为O(1),但不是完全二叉树,且平衡性没有AVL树优秀),所以总体复杂度为O(nlog n)。