1.什么是插入排序
插入排序法每次只处理当前的那个元素,例如将数组进行遍历,第N轮循环,第N个元素和之前的所有元素进行对比,然后插入到自己合适的位置.
例如我有一个有序数组4,6,2,3,4,5;第一轮获取到第一个元素4,他为第一个元素,不需要对比,第二轮获取到6,6和前面的元素4对比,比4大,不需要动.第三轮获取到元素2,他先和6对比,发现需要插入到6前面,然后再和4对比,发现又需要插到4前面,就会变为2,4,6,3,1,5;如此类推.
2.插入排序和选择排序的比较
我们发现插入排序和选择排序经过I轮循环之后arr[0...i) 是有序的;arr[i...n) 是无序的.
插入排序法每次只处理当前的那个元素,把当前的这个元素放到合适的位置,插入排序法永远不会动i这个索引还没有遍历到的那些元素.
插入排序法在完成四轮循环后处理的就是数组原本的前四个元素,把原本的数组的前四个元素排好序,而选择排序法前半部分排好序之后这个排序的结果也是整个数组最终排好序的排序结果.
得出结论,虽然选择排序与插入排序都是 [0,i) 排了序, [ i,n] 没有排序。区别在于,选择排序暂时排好的数据就是最终排好序的位置(因为获取元素的时候,和数组每个元素都去比较了一遍),但是插入排序暂时排好序的数据最终不一定在他所在的位置(因为只和他前面位置的元素去比较,而不是数组所有元素都比较了一遍).
3.插入排序代码实现
public <E extends Comparable<E>>void mySort(E[] arr){
//5,4,6,7,3,1,2
for (int i = 0; i < arr.length; i++) {
for (int j=i;j-1>=0;j--){
if(arr[j].compareTo(arr[j-1])<0){
swap(arr,j,j-1);
}else {
break;
}
}
}
}
private static <E> void swap(E[] arr, int i, int j){
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
4.优化插入排序
我们发现如果每次比较之后都需要交换元素,交换元素进行了3次赋值操作(swap方法).我们做一下优化,下面有数组2,4,6,3,1,5,假设我们现在在第四轮循环获取到元素3,我们先把3暂存起来,然后和6比较,6比3大,向右平移得到2,4,6,6,1,5,只进行了一次赋值操作,然后再和4比较,4比3大,4向右平移,也是只进行了一次赋值操作,得到2,4,4,6,1,5,然后和2比较,3比2大,所以我们直接把暂存的3赋值给第二个位置变为2,3,4,6,1,5.
改进后的插入排序法 并没有改进时间复杂度仍然是T=O(n2)平方的时间复杂度
/**
* 使用赋值的方式完成排序
*/
public static <E extends Comparable<E>> void sort2(E[] arr) {
int i,j;
E t;
for (i=0; i<arr.length; i++) {
t = arr[i];
for (j=i; j-1>=0; j--) {
// 总是和t相比较,而不是和arr[i]比较,因为平移之后arr[i]会被改变
if (t.compareTo(arr[j-1]) < 0) {
// 左边的向右边平移
arr[j] = arr[j-1];
} else {
break;
}
}
//循环结束,此时的j就应该是要插入的位置
arr[j] = t;
}
}
5.插入排序特性
对于无序数组,选择排序和插入排序的时间复杂度都是O(n^2)级别,
对于有序数组,插入排序的时间复杂度为O(n)级别(对于近乎有序的数组,插入排序是优于选择排序的),而选择排序是稳定的O(n^2)
时间复杂度只考虑最坏的情况