算法原理
将数组看作左右两个部分,保持数组左边有序,将右边的每个数字依次插入到左边的相应位置。
算法实现
上述实现中,对数组第i位的元素不断向前交换,直到无法继续向前交换,此时第i位之前的数组有序。
算法优化
上述算法实现中进行交换操作导致开销很大。其实可以使用右移操作代替交换操作,首先记下第i位的值,将左边的数组逐个右移,直到找到合适的位置,将之前记下的值放入即可。
由于内层循环序列range(i,0,-1)
的最小值为1,导致无法把数字放到数组的首位,因此需要进行额外的判断,如果取出的数字在完成内层循环后没有被放入任何位置,说明只能放在数组首位。
上述优化通过进行位置移动来代替交换操作,有效降低了赋值语句的执行次数。
算法测试
对插入排序算法及同为$ O(N^2) $复杂度的选择排序算法进行了正确性及性能测试,以下是测试结果。
将上述数据制成表格:
数据量 |
特点 |
插入排序 |
优化后的插入排序 |
选择排序 |
优化后的选择排序 |
1000 |
随机[0-1000] |
0.047142s |
0.035747s |
0.036213s |
0.029559s |
10000 |
随机[0-10000] |
4.579244s |
3.236195s |
3.242478s |
2.578856s |
1000 |
随机[0-3] |
0.030886s |
0.023929s |
0.030851s |
0.023379s |
1000 |
近乎有序 |
0.009190s |
0.007998s |
0.027903s |
0.023745s |
对这两种排序算法进行比较,可以注意到两种算法的时间开销在同一级别,选择排序在标准测试中拥有稍好的性能。
但值得注意的是,插入排序对于近乎有序的数组有着明显的性能优势,这是由于插入排序是将第i位的数字向前交换,如果数组近乎有序,交换的次数将会很少,从而节约大量时间。
算法复杂度
插入排序需要使用两层循环遍历,因此时间复杂度为$ O(N^2) $,未使用额外空间,空间复杂度为$ O(1) $。对于插入算法的优化仅减少了赋值操作的次数,没有改变算法的整体结构,因此时间复杂度为$ O(N^2) $,空间复杂度为$ O(1) $。