堆排序(Java) – 虽快,但非主业
堆排序是一个O(nlog(n))级别的算法,因为堆的特性,决定其的级别,好歹是个完全二叉树,怎么滴都带着二分的特性,O(nlog(n))顺理成章了;实现堆排序的算法有很多,这里列举了两种,都是用最大堆来实现的,会用到 最大堆实现
两种堆排序都用到了上面的最大堆实现中的最大堆类,可点击上面的链接查看~~
堆排序 – 反向复制
反向复制玩法思路是将数组中的元素逐个插入到最大堆中,因为最大堆的性质,所有元素插入后形成了一个最大堆,然后逐个元素再从最大堆中弹出,因为弹出的是这个序列的最大值,所以反向复制到原数组中,逐个弹出,原数组就是从小到大的序列了;因为要创建另一个数组作为堆,反向复制玩法的空间复杂度是O(n),实际复杂度,因为每次插入元素和获取元素都需要维护,维护的时间复杂度是O(log(n)),数据量是n个,所以整体时间复杂度是O(n(log(n))
// 堆排序,玩法一,先将元素逐个插入最大堆,然后再弹出并反向复制回到原数组,就是从小到大有序的数组了
import heapSort.heap.MaxHeap;
public class HeapSortBySingle {
private HeapSortBySingle() {}
// 传入数组从0号位开始,堆中从1号位开始,所以-1来对应
public static void sort(Comparable[] arr) {
int n = arr.length;
MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);
for(int i=1; i<=n; i++)
maxHeap.insertELE(arr[i-1]);
for(int i=n; i>0; i--)
arr[i-1] = maxHeap.popMax();
}
}
堆排序 – 逐级最大堆化
逐级最大堆化的思路:所有叶节点都是一个最大堆,如果叶节点在k层,往上一级k-1层进行最大堆变换,那么k-1层往下都是最大堆了,再往上层走,直到k=1,整个堆就是最大堆了,之所以逐层往上的过程中只需要比较当前节点的两个子节点,这两个子节点肯定是其各自分支中最大的这个排序的具体实现在最大堆实现中第二个构造方法中,这里主要给出接口~~
// 堆排序,玩法二,自下向上逐级最大堆化,这个思路称为heapify
import heapSort.heap.MaxHeap;
public class HeapSortByWhole {
private HeapSortByWhole() {}
public static void sort(Comparable[] arr) {
int n = arr.length;
MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(arr);
// 传入数组从0号位开始,堆中从1号位开始,所以-1来对应
for(int i=n; i>0; i--)
arr[i-1] = maxHeap.popMax();
}
}
堆排序性能PK赛
比赛规则:
- 对1000万个数据进行排序;
- 归并排序和快速排序为本次PK赛的特约参赛队员;
- 三场比赛,分别是随机序列、基本有序序列、大量元素相同序列;
- 基本有序序列的未有序数据数量是200个;
- 从上到下是归并排序、快速排序、堆排序-- 反向复制、堆排序-- 逐级最大堆化、堆排序-- 原地变有序;
随机数序列:
MergeSort : 845ms
QuickSort : 3192ms
HeapSortBySingle : 12759ms
HeapSortByWhole : 12780ms
基本有序序列:
MergeSort : 398ms
QuickSort : 758ms
HeapSortBySingle : 4044ms
HeapSortByWhole : 3157ms
基本相同序列:
MergeSort : 1588ms
HeapSortBySingle : 5248ms
HeapSortByWhole : 5165ms
比赛结果:
- 从时间上可以看出,堆排序和归并排序,快速排序是同一数量级的排序算法,都是O(nlog(n))级别;
- 堆排序的速度明显要比归并排序和快速排序慢一些,这也是为什么说堆排序虽快,但非主业;
- 堆排序在面对不同的序列类型时用时都差不多,在这个上面表现还算稳定;
- 彩蛋:同样的算法C++的实现明显比Java实现快了几倍