堆排序
堆排序
前情提要
- 堆排序是利用堆这种数据结构而设计的一种算法
- 堆是具有以下性质的完全二叉树:
(1)每个结点的值都大于等于其左右子节点的值,称为大顶堆
(2)每个结点的值都小于等于其左右子节点的值,称为小顶堆 - 采用堆排序时,一般升序使用大顶堆;降序使用小顶堆
基本思想
- 将待排序序列构造成一个大顶堆;此时,整个数列的最大值就是堆顶的根节点
- 将堆顶元素与末尾元素进行交换,此时末尾元素就成为了最大值,然后将剩余的n-1个元素重新构造成一个堆,这样会得到第n个元素的次小值,如此反复执行,数组将成为一个有序数组。
实现步骤
-
将待排序序列构造成一个大顶堆
(使用for循环,自下向上,自左向右,查找非叶子节点,进行此叶子节点为根节点的树的大顶堆构成,)第一个非叶子节点取法:i=arr.length/2-1,之后逐步减一直至i=0
-
此时,数组中最大的数在index=0的位置,将其与数组最后一个元素交换,交换后大顶堆构成被破坏,对前数组长度减一个元素重新进行大顶堆构成,由于只有树的顶部不符合大顶堆构成,故只需要对index=0进行一次大顶堆构成
-
循环操做步骤2,从数组的最后一个元素开始,逐步递减,直至数组的第二个元素结束(for(int i=arr.length-1;i>0;i--))。此操作完成后数组将排序完成
-
重要步骤:大顶堆如何构成,传入节点i,构造以i为根节点的树的大顶堆:
(1)获取i的左右子节点,比较拿到最大的
(2)将拿到的最大子节点与i进行比较,若子节点大于当前节点,值交换,并将当前结点变为i进行下一次循环。否则直接跳出循环。
代码实现
//编写一个堆排序的方法
//调用对排序方法实现数组的升序排序
public static void main(String[] args) {
int[] arr={4,6,8,5,9};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void heapSort(int[] arr){
//我们将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
for(int i=arr.length/2-1;i>=0;i--){//arr.length/2-1取的是最后一个非叶子节点的节点
adjustHeap(arr,i,arr.length);
}
//交换
for(int j=arr.length-1;j>0;j--){
arr[0]=arr[0]^arr[j];
arr[j]=arr[j]^arr[0];
arr[0]=arr[0]^arr[j];
/**
* 上面的for循环已经将树调整为了大顶堆,
* 在此次for循环交换中只是破坏了其树的根的元素不符合大顶堆;
* 因此只需完成i=0时的二叉树调整
*/
adjustHeap(arr,0,j);
}
}
//将一个数组(二叉树)调整成一个大顶堆
/**
*功能:完成将以i为根节点的树的大顶堆实现
* @param arr:待排序数组
* @param i:表示非叶子节点在数组中的下标
* @param length:对多少个元素继续调整,length在不断减少
*/
public static void adjustHeap(int[] arr,int i,int length){
int temp=arr[i];
for(int k=2*i+1;k<length;k=2*i+1){// k=2*i+1:i的左子节点
if(k+1<length && arr[k+1]>arr[k]){//k+1:i的右子节点;当i的右子节点存在且大于其左子节点时,k++;
k++;
}
if(arr[i]<arr[k]){
arr[i]=arr[k];
i=k;
}else {
break;//此处可以break的原因:调整树时,是从下向上,从左到右。
}
//当for循环结束一次后,我我们已经将以i为父节点的树的最大值跳到了最顶部
arr[i]=temp;
}
}
代码实现图解
复杂度与稳定性
稳定性:若数组中出现重复元素,若排序后重复元素还按照源数组中的先后顺序排列,则称之为稳定,否则为不稳定
in-place:不占用额外内存
n:数据规模
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | In-place | 不稳定 |