堆排序(Heap Sort)
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
1 算法描述
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
2 动图演示
3 代码实现
public class Sort {
public void heapSort(int[] nums) {
int length=nums.length;
for(int i=length/2-1;i>=0;i--){
heapAdjust(nums,i,length);
}
for(int i=length-1;i>0;i--){
int temp=nums[0];
nums[0]=nums[i];
nums[i]=temp;
heapAdjust(nums,0,i);
}
}
private void heapAdjust(int[] nums, int index, int length) {
int max=index;
int l=max*2+1;
int r=max*2+2;
if(r<length&&nums[r]>nums[max]){
max=r;
}
if(l<length&&nums[l]>nums[max]){
max=l;
}
if(max!=index){
int temp=nums[max];
nums[max]=nums[index];
nums[index]=temp;
//交换完之后需要再次对max进行调整,因为此时max有可能不满足最大堆
heapAdjust(nums,max,length);
}
}
}
4 测试
import org.junit.Test;
public class TestSort {
private static int[] tem=new int[]{1,5,9,4,3,6,8,2,6,8};
private static Sort sort=new Sort();
@Test
public void testHeapSort(){
System.out.println("排序前:");
toArray();
sort.heapSort(tem);
System.out.println("排序后:");
toArray();
}
public static void toArray(){
for(int i=0;i<tem.length;i++){
System.out.print(tem[i]+" ");
}
System.out.println();
}
}
结果截图:
5 算法分析
5.1 时间复杂度
建堆的时候初始化堆过程(HeapAdjust)
是堆排序的关键,时间复杂度为O(log n)
,下面看堆排序的两个过程;
第一步,初始化堆,这一步时间复杂度是O(n)
;
第二步,交换堆顶元素过程,需要用到n-1次循环,且每一次都要用到(HeapAdjust)
,所以时间复杂度为((n-1)*log n)~O(nlog n)
;
最终时间复杂度:O(n)+O(nlog n)
,后者复杂度高于前者,所以堆排序的时间复杂度为O(nlog n);
5.2 空间复杂度
空间复杂度是O(1)
,因为没有用到额外开辟的集合空间。
5.3算法稳定性
堆排序是不稳定的,比方说二叉树[6a,8,13,6b],(这里的6a和6b数值上都是6,只不过为了区别6所以这样)经过堆初始化后以及排序过后就变成[6b,6a,8,13];可见堆排序是不稳定的。
6 总结
堆排序不同于其他排序算法,它利用了完全二叉树的性质进行排序,如果能充分理解,那就觉得非常有意思的!本文通过图解方式,笔者尽最大努力讲解,但能力有限,若大家有更好的想法欢迎交流~