堆排序
- JAVA实现
- 和快速排序区别
二叉堆的构建,删除,调整是实现堆排序的基础
之前博客写了二叉堆: 二叉堆
- 最大堆的堆顶是整个堆中的最大元素。
- 最小堆的堆顶是整个堆中的最小元素。
堆排序步骤:
- 把无序数组构建成二叉堆。(需要从小到大排序,则构建成最大堆;需要从大到小排序,则构建成最小堆。)
- 循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶。
最后删除的元素依次组成了有序的数列。
第一步:删除一个最大堆的堆顶(并不是完全删除,而是跟末尾的节点交换位置)
第二步:再过自我调整,第2大的元素就会被交换上来,成为最大堆的新堆顶
先删除堆顶元素(跟末尾的节点交换位置)
再自我调整(第2大的元素就会被交换上来)
由于二叉堆的这个特性,每一次删除旧堆顶,调整后的新堆顶都是大小仅次于旧堆顶的节点。那么只要反复删除堆顶,反复调整二叉堆,所得到的集合就会成为一个有序集合:
删堆顶9,8变成新堆顶
删除节点8,节点7成为新堆顶。
。。。。循环往复
删除3,
原本的最大二叉堆已经变成了一个从小到大的有序集合。
二叉堆实际存储在数组中,数组中的元素排列如下
JAVA实现
package mysort.heapSort;
import java.util.Arrays;
//构建最大堆
public class heapSort {
/**
* “下沉”调整(删除元素)
* @param array 待调整的堆
* @param parentIndex 要“下沉”的父节点
* @param length 堆的有效大小
*/
public static void downAdjust(int[]array,int parentIndex,int length){
// temp 保存父节点值,用于最后的赋值
int temp = array[parentIndex];
int childIndex = 2*parentIndex+1;
while (childIndex<length){
// 如果有右孩子,且右孩子大于左孩子的值,则定位到右孩子
//找的是左右孩子里面最大的孩子
if (childIndex+1<length&&array[childIndex+1]>array[childIndex]){
childIndex++;
}
// 如果父节点大于任何一个孩子的值,则直接跳出
//说明父节点比子节点大了,就没必要交换了(构建最大堆)
if(temp>=array[childIndex]){
break;
}
///无须真正交换,单向赋值即可
array[parentIndex] = array[childIndex];
//再向下找,直到childIndex>=length,结束
parentIndex = childIndex;
childIndex = 2*childIndex+1;
}
//最后再把初始的父节点值赋值过去
array[parentIndex]=temp;
}
/**
* 构建最大堆
* @param array 待调整的堆
*/
public static void buildHeap(int[] array){
// 从最后一个非叶子节点(array.length-2)/2)开始,依次做“下沉”调整
for (int i = (array.length-2)/2;i>=0;i--){
downAdjust(array,i,array.length);
}
}
/**
* 用最大堆排序(升序)
* @param array 待调整的堆
*/
public static void heapSort(int[]array){
// 1. 把无序数组构建成最大堆
buildHeap(array);
System.out.println("构建成功,最大堆是:" + Arrays.toString(array));
//执行堆排序(把堆顶元素删除,放到末尾)
for (int i =array.length-1;i>0;i--){
// 最后1个元素和第1个元素进行交换
int temp = array[i];
array[i] = array[0];
array[0] = temp;
//再把堆顶元素下沉,调整最大堆
//这里,注意:i
//随着i--.之前的最后的几位就保存排序好的数字了,就不动了
downAdjust(array,0,i);
}
}
public static void main(String[] args) {
int[] arr = new int[]{1,3,2,6,5,7,8,9,10,0};
heapSort(arr);
System.out.println("堆排序之后的结果为: "+Arrays.toString(arr));
}
}
最大堆排序(升序排序)
空间复杂度是O(1)
时间复杂度:
- 把无序数组构建成二叉堆,这一步的时间复杂度是O(n)。
- 循环删除堆顶元素,并将该元素移到集合尾部,调整堆产生新的堆顶。需要进行n-1次循环。每次循环调用一次downAdjust方法,所以第2步的计算规模是 (n-1)×logn ,时间复杂度为O(nlogn)。(二叉堆的节点“下沉”调整(downAdjust 方法)是堆排序算法的基础,时间复杂度是O(log n)。)
- 两个步骤是并列关系,所以整体的时间复杂度是O(nlogn)。
和快速排序区别
堆排序和快速排序的平均时间复杂度都是O(nlogn),并且都是不稳定排序。
至于不同点,快速排序的最坏时间复杂度是O(n2),而堆排序的最坏时间复杂度稳定在O(nlogn)。