直接插入排序
前面文章已经讲完了交换类排序,接下来开始学习插入类排序。顾名思义,所谓插入排序指我们会为每一个数据安排一个适合它的位置并将其插入,直到所有数据就位则排序完成。
直接插入法便是插入排序的典型方法,完全继承了插入排序的“脾气”:简单、粗暴,逮到就插入,毫无技术可言,耿直得可爱。
1. 算法思想
将待排序数组中的记录逐一插入已排序的子序列中,从而得到一个完整的有序数组。
2. 算法步骤
(1)将数组的第一个数据看成一个有序的子序列。
(2)从第二个数据开始,依次与前面的有序子序列进行比较,若待插入数据 array[i]大于或等于array[i-1] ,则原位插入:若待插入数据 array[i]小于数据 array[i-1],则将数据array[i]临时存放在哨兵t emp中,将有序序列中大于哨兵的所有数据后移一位,然后将哨兵数据插入相应位置。
(3)重复步骤(2),直到整个数组变成有序数组。
3. 算法分析
也许有的读者看得有点糊涂了。为了便于理解,我们将步骤进行拆分讲解。假设我们要给数组[3,1,8,2,1,5]进行排序,由于有两个1,所以将第二个1标记为“1*”,排序过程如下表所示。
排序过程 | 哨兵temp | 数组array |
---|---|---|
1 | 1 | [3] 1 8 2 1* 5 |
2 | 8 | [1 3] 8 2 1* 5 |
3 | 2 | [1 3 8] 2 1* 5 |
4 | 1* | [1 2 3 8] 1* 5 |
5 | 5 | [1 1* 2 3 8] 5 |
6 | - - | [1 1* 2 3 5 8] |
以上是直接插入排序的过程。接下来再细分每一步的比较移位过程。以i=3例,此时 array[0]~ array[2]已经排好序,为[1,3,8]。因为 array[3]<array[2],所以需要将 array[3]临时存储到哨兵 temp 中,以防止 array[2]后移时将 array[3]覆盖。然后从 array[2]~ array[0]中依次寻找插入的位置,执行“比较⋯⋯后移⋯⋯比较⋯⋯后移⋯⋯”操作,直到小于或等于 temp 的值出现或有序子序列已遍历结束,最后将 temp 插入,则 array[0]~array(3]序列的排序便完成,如下表所示。
移位过程 | 哨兵temp | 数组array |
---|---|---|
1 | - - | [1 3 8] 2 1* 5 |
2 | 2 | [1 3 8] 2 1* 5 |
3 | 2 | [1 3 8 8] 1* 5 |
4 | 2 | [1 3 3 8] 1* 5 |
5 | 2 | [1 2 3 8] 1* 5 |
当待排序数组为正序,即数组中的所有元素均已按照以小到大的顺序排好时,只需进行n-1次循环即可,每次循环只需与前面有序子序列的最后一个元素进行一次比较并且无须移动元素。此时,比较次数 C=n-1 和移动次数 M=0均为最小值,时间复杂度力O(n),是最好的情况。
当待排序数组为逆序时,同样需要进行n-1次循环,但每次循环均需要将待插入数据与有序子序列array[0,i-1]中的第i个元素进行比较,并且每比较一次便需要做一次数据移动。此时,比较次数为:
C
m
a
x
=
∑
i
=
1
n
−
1
i
=
n
(
n
−
1
)
2
≈
n
2
2
C_{max}=\sum_{i = 1}^{n-1}{i}=\frac{n(n-1)}{2}\approx \frac{n^2}{2}
Cmax=i=1∑n−1i=2n(n−1)≈2n2
移动次数为:
M
m
a
x
=
∑
i
=
1
n
−
1
(
i
+
2
)
=
(
n
+
4
)
(
n
−
1
)
2
≈
n
2
2
M_{max}=\sum_{i = 1}^{n-1}{(i+2)}=\frac{(n+4)(n-1)}{2}\approx \frac{n^2}{2}
Mmax=i=1∑n−1(i+2)=2(n+4)(n−1)≈2n2
此时,比较和移动的次数均为最大值,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),是最坏的情况。因此直接插入排序的平均时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
注意:i+2中的2 即表示temp=array[i]和 array[i]=temp 这两次赋值行为。
这里我们只使用了i、j和 temp这三个辅助变量,与问题的规模无关,因此空间复杂度为O(1)。且由于相同元素的相对位置不变,所以直接插入排序属于稳定排序。
4. 算法代码
算法代码如下:
源码下载
Python
java
5. 输出结果
6.算法过程