概述:
插入排序是一种最简单的排序方法之一,它的基本思想是将一个记录插入到已经排序号的有序表中,从而获得一个新的、记录数增1的有序列表。通过双城循环的方法进行实现,外层循环用来标志当前待排序的数组,内层循环用来查找当前数字在有序序列中的正确位置,并进行相应的移动。规定一个为i的指针,这个指针指向当前需要排序的序列中的值,当指向的这个值比它左侧的数据小的时候,将当前这个值,与其左侧的数字进行交换,再判断当前这个数字是否比其左侧的数字小,如果小再进行交换,不断的重复这个过程,直到左侧没有数据,或者左侧数据比当前的待排序的数值小为止。并将指针指向下一个待排序的数值。不断的重复这个过程直到全部排序完成为止。
循环不变量:
arr[0,i)已排好序;arr[i...n)未排序;把arr[i]放到合适的位置。
插入排序与选着排序的区别:
这两种算法最大的却别在于待排序数字的左侧已有序的数组是否是全局有序的,选择排序每一次都是获取的当前待排序列中最小值,依次排在左侧,所以这个选择排序的左侧是有序序列是全局有序的,而插入排序只是针对当前已排序的值有序。
实例代码:
数组生成类实例:
public class ArrayGenerator { private ArrayGenerator(){} public static Integer[] generateOrderedArray(int n){ Integer[] arr = new Integer[n]; for(int i=0; i < n; i++) { arr[i] = i; } return arr; } // 生成一个长度为n的随机数组,每个数字的范围是[0,bound) public static Integer[] generateEandomArray(int n,int bound){ Integer[] arr = new Integer[n]; Random rnd = new Random(); for(int i=0; i<n; i++){ arr[i] = rnd.nextInt(bound); } return arr; } }
排序辅助类:
public class SortingHelper { private SortingHelper(){} //用以验证排序后的数组是否是有序数组 public static <E extends Comparable> boolean isSorted(E[] arr){ for(int i = 1;i<arr.length; i++){ if(arr[i - 1].compareTo(arr[i])>0){ return false; } } return true; } //验证算法的时效性 public static <E extends Comparable> void sortTest(String sortName,E[] arr){ Long startTime = System.nanoTime(); if(sortName.equals("SelectionSort")){ ChoiceTest.genericSort(arr); }else if(sortName.equals("InsertionSort")){ InsertTest.sort(arr); }else if(sortName.equals("InsertionSortPlus")){ InsertTest.sortPlus(arr); } Long endTime = System.nanoTime(); double time = (endTime - startTime) / 1000000000.0; if(!SortingHelper.isSorted(arr)){ throw new RuntimeException(sortName+" failed!"); } System.out.println(String.format("%s , n = %d : %f s",sortName, arr.length,time)); } }
插入算法测试实体类:
public class InsertTest { public static void main(String[] args) { int [] dataSize = {10000,100000}; for(int n : dataSize){ Integer[] arr = ArrayGenerator.generateEandomArray(n,n); Integer[] arr2 = Arrays.copyOf(arr,arr.length); SortingHelper.sortTest("InsertionSort",arr); SortingHelper.sortTest("InsertionSortPlus",arr); } } public static<E extends Comparable<E>> void sort(E[] arr){ for(int i = 0; i < arr.length; i++){ //将 arr[i] 插入到合适的位置 /* for(int j = i; j - 1 >= 0; j--){ if(arr[j].compareTo(arr[j - 1]) < 0){ swap(arr,j,j-1); }else{ break; } } */ for(int j=i; j-1 >= 0 && arr[j].compareTo(arr[j - 1]) < 0; j --) swap(arr,j,j-1); } } public static <E extends Comparable<E>> void sortPlus(E[] arr){ for(int i = 0; i < arr.length; i ++){ //将arr[i] 插入到合适的位置 E t = arr[i]; int j; /** * 相比较选择排序,插入排序存在一个内层循环提前终止的机制。 * 假设插入排序的数组有一定的顺序性,比如:1,2,3,4,5,6。 * 这种情况下内层循环只是查询了一个指针指向的那个元素, * 所以对于有序数组来说,插入排序的复杂度就变成了O(n) * 选择排序法就做不到这一点,选择排序算法的每一次都要查询一遍 * 后面的数据才能确认是否为最小值,即便当前数据就是最小值。 * * 但是,整体而言插入排序的复杂度依然是O(); * */ for(j = i; j - 1 >= 0 && t.compareTo(arr[j - 1]) < 0; j --){ arr[j] = arr[j -1]; } arr[j] = t; } } private static <E> void swap(E[] arr,int i,int j){ E t = arr[i]; arr[i] = arr[j]; arr[j] = t; }
从算法的实例代码可以看到,插入排序算法也是使用了两层for循环,所以插入排序算法复杂度也是O(),但是插入排序算法有一种特殊的情况,也就是如果待排序数组本身就是有序的情况下,内层for循环只是起到了查询的作用,针对这种情况插入排序的复杂度编程了O(n),这是一种极端的特殊情况,但是整体而言插入排序的复杂度依然是O(
);