辅助工具
数组工具类代码 : https://blog.csdn.net/love905661433/article/details/83106078
选择排序
-
O(n²)级别算法
-
以从小到大排列为例, 每次循环找到剩余未排序数组中最小的值, 和为排序数组的第一个位置交换位置
如针对数组[3, 4, 1, 8, 7], 排序步骤如下:
- 第一次循环结束 : [1, 4, 3, 8, 7]
- 第二次 : [1, 3, 4, 8, 7]
- 第三次 : [1, 3, 4, 8, 7], 由于4已经是剩下的里面最小的了, 这次不需要交换
- 第四次 : [1, 3, 4, 7, 8], 其实这一次已经是顺序的了
- 第五次 : [1, 3, 4, 7, 8]
代码实现
/**
* 对int类型数组arr进行排序
* @param arr
*/
@Override
public void sort(int[] arr){
for (int i = 0; i < arr.length; i++) {
// 保存最小元素所在位置
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 自定义的用于数组元素交换的函数
ArrayUtil.swap(arr, i, minIndex);
}
}
插入排序
-
O(n²)级别算法
-
插入排序对近乎有序的数组排序效率非常高, 甚至超过O(nlogn)级别的算法
-
从后往前, 每次排序都选择当前元素应该插入的位置
针对数组[8, 6, 2, 3, 1, 5, 7, 4], 第一个元素默认位置是正确的, 从第二个元素开始排序, 进行插入排序之后, 每次循环结束排序结果如下 :
- [6, 8, 2, 3, 1, 5, 7, 4], 找到元素6应该在的位置6,8交换位置
- [2, 6, 8, 3, 1, 5, 7, 4], 找到元素2应该在的位置, 交换2,8位置, 交换2,6位置
- [2, 3, 6, 8, 1, 5, 7, 4], 找到元素3应该在的位置, 交换3,8位置, 交换3,6位置
- [1, 2, 3, 6, 8, 5, 7, 4], 找到元素1应该在的位置, 1与8,6,3,2依次交换位置
- [1, 2, 3, 5, 6, 8, 7, 4], 找到元素5应该在的位置, 5与8,6依次交换位置
- [1, 2, 3, 5, 6, 7, 8, 4], 找到元素7应该在的位置, 7与8交换位置
- [1, 2, 3, 4, 5, 6, 7, 8], 找到元素4应该在的位置, 4与8,7,6,5依次交换位置
代码实现
代码实现1 :
public void sort(int [] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
ArrayUtil.swap(arr, j, j -1);
} else {
break;
}
}
}
}
对于上述的实现方法, 每次比较时都要进行交换操作, 而交换操作是比较耗性能的, 可以考虑用一个变量记录元素i应该插入的位置, 找到具体应该插入的位置时, 再进行一次插入操作, 性能会更好, 代码如下:
public void sort(int [] arr) {
for (int i = 0; i < arr.length; i++) {
int temp = arr[i];
// 保存元素i应该插入的位置
int j;
// arr[j - 1] > temp表示还未找到元素i应该插入的位置
for (j = i; j > 0 && arr[j - 1] > temp ; j--) {
// 未找到元素i应该插入的位置之前, 前面的元素只要每次往后移动一位即可
arr[j] = arr[j - 1];
}
// 循环结束, j的位置就是元素i应该插入的位置
arr[j] = temp;
}
}
测试
使用下面的测试代码对选择排序和两种插入排序的性能做一个简单的测试 :
/**
* 测试选择排序和两次插入排序性能
*/
@Test
public void testSort(){
// 生成一个随机数组
int[] arr = ArrayUtil.generateArray(100000, 0, 100000);
int[] arr1 = ArrayUtil.copyArray(arr);
int[] arr2= ArrayUtil.copyArray(arr);
System.out.println("随机数组->选择排序 : " + ArrayUtil.testSort(arr, new SelectionSort()) + "s") ;
System.out.println("随机数组->插入排序1 : " + ArrayUtil.testSort(arr1, new InsertSort()) + "s") ;
System.out.println("随机数组->插入排序2 : " + ArrayUtil.testSort(arr2, new InsertSort2()) + "s") ;
/*
* 生成一个近乎有序的数组
* 100000 : 数组元素个数
* 10 : 在一个完全有序的数组上进行多少次元素交换
*/
arr = ArrayUtil.generateNearlyOrderedArray(100000, 10);
arr1 = ArrayUtil.copyArray(arr);
arr2= ArrayUtil.copyArray(arr);
System.out.println("近乎有序的数组->选择排序:" + ArrayUtil.testSort(arr, new SelectionSort()) + "s") ;
System.out.println("近乎有序的数组->插入排序1:" + ArrayUtil.testSort(arr1, new InsertSort()) + "s") ;
System.out.println("近乎有序的数组->插入排序2:" + ArrayUtil.testSort(arr2, new InsertSort2()) + "s") ;
}
执行结果 :
随机数组->选择排序 : 2.435s
随机数组->插入排序1 : 3.605s
随机数组->插入排序2 : 2.429s
近乎有序的数组->选择排序:2.397s
近乎有序的数组->插入排序1:0.003s
近乎有序的数组->插入排序2:0.002s从上述输出结果来看可以发现, 在对于近乎有序的数组进行排序的话, 插入排序性能是很高的, 特别是优化后的插入排序
冒泡排序
- O(n²)级别算法
- 每次比较对比相邻两个元素位置, 如果后面的元素大于前面的元素, 则进行位置交换, 代码如下 :
代码实现
代码实现1 :
@Override
public void sort(int [] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
ArrayUtil.swap(arr, j, j + 1);
}
}
}
}
针对上述排序算法, 可能会存在如果循环进行了一半, 其实数组已经有序了, 但是仍然会将剩下的循环进行到底, 就会浪费一些性能, 可以增加一个参数用来标识数组是否有序了, 如果数组已经有序了, 就不再进行循环了, 直接返回, 优化后代码如下 :
public void sort(int [] arr) {
for (int i = 0; i < arr.length - 1; i++) {
// 用于记录数组是否已经是有序的了
boolean isSorted = true;
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果本次循环数组发生交换, 就认为数组还不是有序的
if (arr[j] > arr[j + 1]) {
ArrayUtil.swap(arr, j, j + 1);
isSorted = false;
}
}
// 如果内存循环结束, isSorted扔为true, 则表示数组已经排序完成
if (isSorted) {
break;
}
}
}
测试
下面通过一个测案例, 对上述选择排序, 插入排序, 冒泡排序的性能进行测试 :
/**
* 测试选择排序和两种插入排序已及两种冒泡排序的性能
*/
@Test
public void testSort(){
// 生成一个随机数组
int[] arr = ArrayUtil.generateArray(100000, 0, 100000);
int[] arr1 = ArrayUtil.copyArray(arr);
int[] arr2 = ArrayUtil.copyArray(arr);
int[] arr3 = ArrayUtil.copyArray(arr);
int[] arr4 = ArrayUtil.copyArray(arr);
System.out.println("随机数组->选择排序 : " + ArrayUtil.testSort(arr, new SelectionSort()) + "s") ;
System.out.println("随机数组->插入排序1 : " + ArrayUtil.testSort(arr1, new InsertSort()) + "s") ;
System.out.println("随机数组->插入排序2 : " + ArrayUtil.testSort(arr2, new InsertSort2()) + "s") ;
System.out.println("随机数组->冒泡排序1 : " + ArrayUtil.testSort(arr3, new BubbleSort()) + "s") ;
System.out.println("随机数组->冒泡排序2 : " + ArrayUtil.testSort(arr4, new BubbleSort2()) + "s") ;
/*
* 生成一个近乎有序的数组
* 100000 : 数组元素个数
* 10 : 在一个完全有序的数组上进行多少次元素交换
*/
arr = ArrayUtil.generateNearlyOrderedArray(100000, 10);
arr1 = ArrayUtil.copyArray(arr);
arr2 = ArrayUtil.copyArray(arr);
arr3 = ArrayUtil.copyArray(arr);
arr4 = ArrayUtil.copyArray(arr);
System.out.println("近乎有序的数组->选择排序:" + ArrayUtil.testSort(arr, new SelectionSort()) + "s") ;
System.out.println("近乎有序的数组->插入排序1:" + ArrayUtil.testSort(arr1, new InsertSort()) + "s") ;
System.out.println("近乎有序的数组->插入排序2:" + ArrayUtil.testSort(arr2, new InsertSort2()) + "s") ;
System.out.println("近乎有序的数组->冒泡排序1:" + ArrayUtil.testSort(arr3, new BubbleSort()) + "s") ;
System.out.println("近乎有序的数组->冒泡排序2:" + ArrayUtil.testSort(arr4, new BubbleSort2()) + "s") ;
}
执行结果 :
随机数组->选择排序 : 2.407s
随机数组->插入排序1 : 3.565s
随机数组->插入排序2 : 2.402s
随机数组->冒泡排序1 : 14.171s
随机数组->冒泡排序2 : 14.414s
近乎有序的数组->选择排序:2.385s
近乎有序的数组->插入排序1:0.002s
近乎有序的数组->插入排序2:0.003s
近乎有序的数组->冒泡排序1:1.903s
近乎有序的数组->冒泡排序2:1.561s
总结
虽然相对于归并排序, 快速排序等O(nlogn)级别的算法来说, 这几种排序算法在性能上有很大差距, 但是也有一定的用处, 特别是插入排序, 在数组数据量较小的情况下, 往往比nlogn级别的算法更快, 所以通常插入排序可以用于归并排序, 快速排序等递归到底层时使用。