1.堆排序的基本介绍
(1)堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
(2)堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子节点的值,称为大顶堆,注意:没有要求节点的左孩子的值和右孩子的值的大小关系。
(3)每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。
(4)大顶堆举例说明:
(5)小顶堆举例说明
2.堆排序的基本思想
(1)将待排序序列构造成一个大顶堆。
(2)此时,整个序列的最大值就是堆顶的根节点。
(3)将其与末尾元素进行交换,此时末尾就为最大值。
(4)然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
3.堆排序的步骤图解
简单总结下堆排序的基本思路:
(1)将无序序列构成一个堆,根据升序降序需求选择大顶堆或小顶堆。
(2)将堆顶元素与末尾元素交换,将最大元素与数组末尾元素交换。
(3)重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换,知道整个序列有序。
4.代码实现
@Test
public void testSort(){
//我们来测一下各种排序速度
int[] array = new int[100000];
for (int i = 0; i < array.length; i++) {
array[i] = (int) (Math.random()*100000);
}
long start = System.currentTimeMillis();
//bubbleSorting(array);//冒泡排序排序100000个数总耗时17984毫秒
//selectSorting(array);//选择排序排序100000个数总耗时3203毫秒
//insertSorting(array);//直接插入排序100000个数总耗时1109毫秒
//shellSortByExchange(array);//希尔的交换排序100000个数总耗时7829毫秒
//shellSortByMove(array);//希尔的移动排序100000个数总耗时16毫秒
//quickSort(array,0,array.length-1);//快速排序的移动排序100000个数总耗时31毫秒
//mergeSort(array,0,array.length-1,new int[array.length]);//归并排序的移动排序100000个数总耗时16毫秒
//cardinalitySort(array);//基数排序的移动排序100000个数总耗时31毫秒
heapSort(array);//堆排序的移动排序100000个数总耗时14496毫秒
long end = System.currentTimeMillis();
System.out.println("总共耗时:"+(end-start));
}
//堆排序
public void heapSort(int[] array) {
int temp = 0;
//得到所有非叶子节点的下标,进行调整
for (int i = array.length/2-1; i >= 0 ; i--) {
adjustHeap(array,i,array.length);
}
//将堆顶元素与数组的末尾元素进行交换
for (int i = array.length-1; i > 0 ; i--) {
temp = array[i];
array[i] = array[0];
array[0] = temp;
//重新进行调整
for (int j = i/2-1; j >= 0 ; j--) {
adjustHeap(array,j,i);
}
}
}
/**
* 将以index为非叶子节点的树调整为最大堆
* 例如,array = {4,6,8,5,9}
* 4
* 6 8
* 5 9
* 传入index=1时,经过adjustHeap方法调整后,array={4,9,8,5,6}
* 4
* 9 8
* 5 6
* 在传入index=0时,经过adjustHeap方法调整后,array={9,6,8,5,4}
* 9
* 6 8
* 5 4
* @param array 待调整的数组
* @param index 非叶子节点在数组中的索引
* @param length 待调整数组元素的个数
*/
public void adjustHeap(int[] array, int index, int length) {
//先取出非叶子节点的值
int temp = array[index];
//i指向当前非叶子节点的左子节点,完全二叉树满足left=2n+1,right=2n+2,
//n为非叶子节点,left和right分别表示左右子节点
for (int i = 2*index+1; i < length; i=2*i+1) {
//比较当前非叶子节点的左右子节点的大小
if (i+1 < length && array[i] < array[i+1]) {
//如果右子节点大那么i就指向右子节点
i++;
}
//比较非叶子节点和子节点的大小
if (array[i] > temp) {
array[index] = array[i];//把非叶子节点和子节点交换
index = i;//index指向交换的子节点
continue;
}
break;
}
//当for循环之后,就已经将以index为根节点的树中的最大值放到了最顶处
//把temp放到调整后的合适位置
array[index] = temp;
}