前面已经讲过 选择排序, 冒泡排序,他们都是基于比较的排序,接下来介绍的插入排序虽然也是基于比较的,但是在某些情况下,他们较少元素之间的比较次数,相较选择和冒泡,排序性能可以降到平方级别以下
插入排序的总体思路为:
每次插入排序都是将某个未排序的元素插入到已经排好顺序的数组里面。
描述:
- 首先我们认为第一个元素已经排好顺序,毫无疑问,第一个元素只有他自己
- 从第N个元素(下文称为:此元素)开始,将此元素与前面索引为N-i 元素依次比较,按照从后往前的顺序比较,如果发现某个元素比此元素大,则将该元素( a[n-i] )往后移动一个位置(实际上是把这个位置空出来,以便下一次找到一个比此元素小的某个元素时,将此元素放在空出来的位置上),如果发现某个元素比此元素小,则将此元素放置在上一次比较时,空出来的位置上。
- 重复上面的步骤,直到循环结束
插入排序的算法实现为:
public class InsertionSort {
public static void main(String[] args) {
int[] nums = new int[]{1, 15, 3, 78, 34, 23, 46, 2, 8, 34, 57};
System.out.println(Arrays.toString(nums));
sort(nums);
System.out.println(Arrays.toString(nums));
}
public static void sort(int[] arrays) {
for(int i=1 ; i< arrays.length; i++) {
int j = i;//record the index of start
int temp = arrays[j];
for(; j> 0; j--) {
if (temp < arrays[j -1]) {
arrays[j] = arrays[j -1];
}else{
break;
}
}
arrays[j] = temp;
}
}
}
同样的,我们如果不通过"元素移动",而是每次比较之后都交换的话代码就会是下面这样:
public class InsertionSort {
public static void main(String[] args) {
int[] nums = new int[]{1, 15, 3, 78, 34, 23, 46, 2, 8, 34, 57};
System.out.println("before insert sort "+Arrays.toString(nums));
sort(nums);
System.out.println("after insert sort "+Arrays.toString(nums));
}
public static void sort(int[] arrays) {
for(int i=1 ; i< arrays.length; i++) {
int j = i;//record the index of start
for(; j> 0; j--) {
if (arrays[j] < arrays[j -1]) {
int temp = arrays[j];
arrays[j] = arrays[j - 1];
arrays[j -1] = temp;
}else{
break;
}
}
}
}
}
显然,通过"移动元素"更快速.
算法复杂度分析:
这张图是从这里 下载的,感兴趣的同学可以自行检索
先解释下这张图,对角线左侧的元素是已经排好序的元素,而对角线右侧的元素时未排序的元素,而且,左侧的元素会移动位置,而右侧的元素不会移动。
红色的元素是每次内循环中找到的需要移动位置的元素,从上完下纵向查看,可以很清楚的知道红色元素就是每次内循环插入的元素。对于每个元素a[i],依次与前面的a[i-1]到a[0]比较,直到找到比a[i]小的元素的位置,然后插入,一直到数组结束。对与每一个元素,它是从当前位置开始从后向前依次查找的过程,直到找到一个比该元素小的元素的位置(有点啰嗦,但过程就是这样的)。
复杂度分为三种情况:
第一种:数组已经排好序,则只需要N-1次比较就好了,完全不需要交换
第二种:数组逆序,则需要 N2/2(等差数列求和)次比较和 N2/2次交换,参考 选择排序,和选择排序的复杂度相当
第三种:平均情况,有一半有序。则需要 N2/4次比较和 N2/4次交换
下篇会比较一下选择排序和插入排序:选择排序和插入排序比较