对于任何一个数组,选择排序两层循环的每一层循环都必须完全的执行完成。正是因为如此,选择排序的效率在任何情况下都是非常慢的。相较之下,插入排序最差的时间复杂度也是O(n^2)级别的,但是在数组近乎有序的情况下,插入排序的性能非常高,甚至会比O(nlogn)级别的排序性能还要高,这使插入排序有非常重要的实际意义。
另外一种排序算法:冒泡排序( BubbleSort)-- 是通常所接触的第一个排序算法。事实上,冒泡排序的性能整体没有插入排序好,不会过多使用。思考并熟悉即可。
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端。
BubbleSort
package bobo.algo; public class BubbleSort { // 我们的算法类不允许产生任何实例 private BubbleSort(){} public static void sort(Comparable[] arr){ int n = arr.length; boolean swapped = false; do{ swapped = false; for( int i = 1 ; i < n ; i ++ ) if( arr[i-1].compareTo(arr[i]) > 0 ){ swap( arr , i-1 , i ); swapped = true; } n --; }while(swapped); } private static void swap(Object[] arr, int i, int j) { Object t = arr[i]; arr[i] = arr[j]; arr[j] = t; } }
Main
package bobo.algo; import java.util.Arrays; public class Main { // 比较SelectionSort, InsertionSort和BubbleSort三种排序算法的性能效率 public static void main(String[] args) { int N = 20000; // 测试1 一般测试 System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]"); Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N); Integer[] arr2 = Arrays.copyOf(arr1, arr1.length); Integer[] arr3 = Arrays.copyOf(arr1, arr1.length); SortTestHelper.testSort("bobo.algo.SelectionSort", arr1); SortTestHelper.testSort("bobo.algo.InsertionSort", arr2); SortTestHelper.testSort("bobo.algo.BubbleSort", arr3); System.out.println(); // 测试2 测试近乎有序的数组 int swapTimes = 100; System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes); arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes); arr2 = Arrays.copyOf(arr1, arr1.length); arr3 = Arrays.copyOf(arr1, arr1.length); SortTestHelper.testSort("bobo.algo.SelectionSort", arr1); SortTestHelper.testSort("bobo.algo.InsertionSort", arr2); SortTestHelper.testSort("bobo.algo.BubbleSort", arr3); return; } }
另外,通过插入排序法还可以引申出一种非常重要的排序算法:希尔排序(ShellSort)。希尔排序整体的思路就是插入排序的延伸,我们在插入排序中,是每一次和之前的一个元素进行比较。而希尔排序,是每一次和之前第h个元素进行比较,同过将一个很大的值从h逐渐缩小到1,一步一步将完全无序的数组变成近乎有序的数组,变成有序性更强的数组,最后当h=1时,最终变成一个排好序的数组,这个过程使整个算法的时间复杂度发生巨变。但是希尔排序相对整个时间复杂度的分析是比较难的,我们选择不同的从h逐渐递减的序列,它的时间复杂度也是不同的。
了解希尔排序的排序过程,实现它:
希尔排序(Shell Sort)也称缩小增量排序,是插入排序算法的一种更高效的改进版本。它是把数列按下标的一定增量分组,对每组使用插入排序算法排序;增量逐渐减少,直至减至1时,整个数列恰被分成一组,算法便终止。
ShellSort
package bobo.algo; public class ShellSort { // 我们的算法类不允许产生任何实例 private ShellSort(){} public static void sort(Comparable[] arr){ int n = arr.length; // 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093... int h = 1; while (h < n/3) h = 3*h + 1; while (h >= 1) { // h-sort the array for (int i = h; i < n; i++) { // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序 Comparable e = arr[i]; int j = i; for ( ; j >= h && e.compareTo(arr[j-h]) < 0 ; j -= h) arr[j] = arr[j-h]; arr[j] = e; } h /= 3; } } }
在实现中,使用了非常常用的让h递减的序列,在这种情况下,希尔排序的时间复杂度能达到O(n^3/2)这样的级别,相较而言,比O(n^2)级别的排序算法改变了非常多。希尔排序本身也是非常实用的方法,因为它的时间复杂度比O(n^2)低,虽然比O(nlogn)高一些,不过实现相对简单,所以在某些环境下,使用希尔排序会是一种首选。不管怎样,对于排序,最优时间复杂度是O(nlogn)级别的。