插入排序
插入排序: 将元素插入到已排序好的元素中,为了给要输入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位。
与选择排序一样,当前索引左边的所有元素都是有序的,但它们的最终位置还不确定,为了给更小的元素腾出空间,它们可能会被移动。当索引到达数组的右端时,数组排序就完成了。
和选择排序不同的是,插入排序所需的时间取决于输入中元素的初始顺序。例如,对一个很大的且其中的元素已经有序(或接近有序)的数组进行排序将会比对随机顺序的数组排序或逆序数组进行排序要快的多。
结论: 对于随机排列的长度为N且主键不重复的数组,平均情况下插入顺序需要N²/4次比较以及N²/4次交换。最坏的情况下需要N²/2次比较和N²/2次交换,最好的情况下需要N-1次比较和0次交换。
插入排序对于实际应用中常见的某些类型的非随机数组很有效。例如,使用插入排序对一个有序数组进行排序,插入排序能立刻发现每个元素都已经在合适的位置上。它的运行时间也是线性的(对于这种数组,选择排序的运行时间是平方级别的)。对于所有主键都相同的数组也会出现相同的情况。
1 public static void sort(Comparable[] a) {
2 //将a按升序排列
3 int L = a.length;
4 for (int i = 1; i < L; i++) {
5 for (int j = i; j > 0 && a[j].compareTo(a[j - 1]) < 0; j--) {
6 //交换位置
7 Selection.exch(a, j, j - 1);
8 }
9 }
10 }
倒置: 指的是数组中的两个顺序颠倒的元素,比如E X A M这四个元素有E-A,X-A,X-M3对倒置。
如果数组中倒置的数量小于数组大小的某个倍数,那么我们就说这个数组是部分有序的。
几种典型的部分有序数组:
- 数组中的每个元素都距离它的最终位置不远
- 一个有序的大数组接一个小数组
- 数组中只有几个元素的位置不正确
插入排序对这样的数组很有效。事实上,当倒置的数量很少时,插入排序可能比任何算法都要快。
结论: 插入排序需要的交换操作和数组中的倒置的数量相同,需要比较的次数大于等于倒置的数量,小于等于倒置的数量加上数组的大小再减一
改进:
要大幅提高插入排序的速度并不难,只需要在内循环中将较大的元素向右移动而不是总是交换两个元素(这样访问数组的次数就能减半)
1 public static void sort(Comparable[] a) {
2 //将a按升序排列
3 int L = a.length;
4 for (int i = 1; i < L; i++) {
5 //将需要要比较的值赋给t
6 Comparable t = a[i];
7 int j = i;
8 for (; j > 0 && t.compareTo(a[j - 1]) < 0; j--) {
9 //如果t小,将较大元素右移
10 a[j] = a[j - 1];
11 }
12 //直到要插入的数是大的时插入到数组
13 a[j] = t;
14 }
15 }
总的来说,插入排序对于部分有序的数组十分高效,也很适合小规模数组。
插入排序不会访问索引右侧的元素,而选择排序不会访问索引左侧的元素。因为插入排序不会移动比被插入元素更小的元素,所以它所需的比较次数平均只有选择排序的一半
比较选择排序和插入排序的效率:
对于随机排序数组,两者的运行时间都是平方级的。在这种输入下,插入排序运行的时间和N²乘以一个小常数成正比,选择排序的运行时间和N²乘以另一个小常数成正比。
结论: 对于随机排列的无重复的主键的数组,插入排序和选择排序的运行时间是平方级的,两者之比是一个较小的常数。(资料:插入排序比选择排序快一倍)