1️⃣参赛选手总览
前四组参赛选手:
👉第一组:插入排序,希尔排序
👉第二组:选择排序,堆排序
👉第三组:冒泡排序,快速排序(划重点)
👉第四组:归并排序
前面几篇博客重点讲解了以下的排序的内容,以及优化过程和性能分析。
到这里,有四位参赛选手最后进入总决赛。他们分别是希尔排序 堆排序 快速排序 归并排序。后面分析总结他们的特性,比较他们的性能。
2️⃣特性总结
时间复杂度和空间复杂度
分类总结
3️⃣性能比较
这里比较的都是对应排序的最佳版本,为了展现最佳性能。
测试使用的类:
/**
* 排序的辅助类
* 生成测试数组以及对排序算法进行测试
**/
public class SortHelper {
// 获取随机数的对象
private static final ThreadLocalRandom random = ThreadLocalRandom.current();
//在[left...right]上生成n个随机数
public static int[] generateRandomArray(int n,int left,int right) {
int[] arr = new int[n];
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(left,right);
}
return arr;
}
/**
* 生成一个大小为n的近乎有序的数组
* @param n
* @param times 交换的次数,次数越小越有序,次数越大越无序
* @return
*/
public static int[] generateSoredArray(int n,int times) {
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = i;
}
// 交换部分元素,交换次数越小,越有序
for (int i = 0; i < times; i++) {
// 生成一个在[0..n]上的随机数
int a = random.nextInt(n);
int b = random.nextInt(n);
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
return arr;
}
// 根据传入的方法名称就能调用这个方法,需要借助反射
// 根据方法名称调用相应的排序方法对arr数组进行排序操作
public static void testSort(String sortName,int[] arr) {
Class<SevenSort> cls = SevenSort.class;
try {
Method method = cls.getDeclaredMethod(sortName,int[].class);
long start = System.nanoTime();
method.invoke(null,arr);
long end = System.nanoTime();
if (isSorted(arr)) {
// 算法正确
System.out.println(sortName + "排序结束,共耗时:" + (end - start) / 1000000.0 + "ms");
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
// 生成一个arr的深拷贝数组
// 为了测试不同排序算法的性能,需要在相同的数据集上进行测试
public static int[] arrCopy(int[] arr) {
return Arrays.copyOf(arr,arr.length);
}
public static boolean isSorted(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
System.err.println("sort error");
return false;
}
}
return true;
}
}
一千万个随机数据
public static void main(String[] args) {
int n = 10000000;
int[] arr = SortHelper.generateRandomArray(n, 0, Integer.MAX_VALUE);
int[] arrCopy4 = SortHelper.arrCopy(arr);
int[] arrCopy6 = SortHelper.arrCopy(arr);
int[] arrCopy7 = SortHelper.arrCopy(arr);
int[] arrCopy10 = SortHelper.arrCopy(arr);
SortHelper.testSort("shellSort", arrCopy4);
SortHelper.testSort("mergeSortVersion2", arrCopy6);
SortHelper.testSort("quickSort", arrCopy7);
SortHelper.testSort("heapSort", arrCopy10);
}
归并排序第二种写法,也就是在一开始就定义和原数据一样大的空间。
快速排序使用的是单路快排,在排序大量的随机数据时,性能是很好的。
而且都在小区间上使用插入排序,尽可能地提升性能。
至于希尔排序和堆排序鉴于算法的原因,所以没有太大的优化空间。
运行结果:
可以看出归并排序和快速排序在此时性能是很好的。
一千万个大量重复数据
int n = 10000000;
int[] arr = SortHelper.generateRandomArray(n,0, 1000);
因为有大量的重复元素,所以不能使用单路快排了,性能衰减,这里换上对大量重复数据排序最有利三路快排,它可以分区一次就把和基准值相同的元素排好,并放在最终的位置上。
运行结果:
由于快排使用了最有利的算法,所以它的性能还是最好的。
一千万个接近有序数据
int[] arr = SortHelper.generateSoredArray(n,1000);
对于这种数据,快排的挖坑法就特别有优势了,因为很多数据都已经有序了,只需要将那些被打乱的数据排序即可,而且挖坑法没有频繁的交换元素,只是将数据赋值,更多的是填坑的过程。
运行结果:
所以对于有些数据,快排的性能会衰减,但是都有相应的算法去解决,所以论速度,快排是当之无愧的。
至于其他的三个排序,性能都比较稳定,如果数据多变的情况下,使用他们也不失为一种好选择。而且归并排序无论在何时都接近快排的性能,并且它还能实现外排序。
在所有的分区设计的排序算法中,都不能保证数据的稳定性,想要数据稳定,还得依靠其他的排序算法。
所以,追求性能的同时,可能会带来一定的弊端,对于不同的场景,选择合适的排序算法,才是正解。
相较于第一届用c代码写的排序大赛,java版本提供了更多的方法和优化思路,同时代码的质量也得到提升。
最后再一次恭喜快排获得冠军
🌸完结撒花 🌸
4️⃣全部源码
需要源码的小伙伴点击以下传送门
👇
🌀排序源码完整版🌀