顾名思义,采用插入的方式,对无序数列进行排序。
维护一个有序区,将数据一个一个插入到有序区的适当位置,直到整个数组都有序。即每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。
先吧首元素 5 作为有序区,此时有序区只有一个元素,如下图所示。
将下一个元素 8 和有序区所有元素依次比较,找到合适的位置,然后插入。
,所以元素 8 和元素 5 不需要交换。这样有序区的元素增加到两个,如下图所示。
将下一个元素 6 和有序区所有元素依次比较,找到合适的位置,然后插入。
,所以元素 6 和元素 8 进行交换,如下图所示。
再比较 6 和 5 ,,所以元素 6 和元素 5 不需要交换。这样有序区的元素增加到三个,如下图所示。
将下一个元素 3 和有序区所有元素依次比较,找到合适的位置,然后插入。
,所以元素 3 和元素 8 进行交换,如下图所示。
,所以元素 3 和元素 6 进行交换,如下图所示。
,所以元素 3 和元素 5 进行交换,如下图所示。
这样有序区的元素增加到四个,如下图所示。
......
依次类推,直到整个数列的元素插入完毕,排序完成。
下图是每一轮插入排序的结果。
说明:上述的排序过程没有记性优化,相关的优化可以参考下面的内容。
注意:插入排序的算法优化不会降低算法的时间复杂度,只是减少了许多无谓的交换。
我们观察一下第三轮操作中,发现需要让元素 3 逐个与有序区的元素进行比较和交换,整个交换过程是:与 8 交换、与 6 交换、与 5 交换,最终交换到有序区的第一个位置。可以发现,并不需要进行着三次交换,算法可以进行优化。
只需要把元素 3 暂时保存起来,先把有序区的元素从左到右逐一复制,再将元素 3 插入到合适的位置即可。具体过程用图示说明。
暂存元素 3 ,入下图所示。
和前一个元素比较,由于 3 < 8,复制元素 8 到它下一个位置。如下图所示。
和前一个元素比较,由于 3 < 6,复制元素 6 到它下一个位置。如下图所示。
和前一个元素比较,由于 3 < 5,复制元素 5 到它下一个位置。如下图所示。
到达有序区的第一位,比较完成。将暂存的元素 3 赋值到数组的首位。如下图所示。
显然,这样的优化减少了许多无谓的交换过程。
关键字比较次数记为C,记录移动次数记为M。
1、当初始序列为正序时,只需要外循环 n-1 次,每次进行一次比较,无需移动元素。此时比较次数 和移动次数 达到最小。
,,此时时间复杂度为。
2、当初始序列为反序时,需要外循环 n-1 次,每次排序中待插入的元素都要和 [0, i-1] 中的 i 个元素进行比较且要将这 i 个元素后移 i 次,加上 tmp = arr[i] 与 arr[j] = temp 的两次移动,每趟移动次数为 i+2,此时比较次数和移动次数达到最大值。
此时时间复杂度为。
在直接插入排序中,需要了三个辅助变量,与数据规模无关,空间复杂度为。
稳定排序算法。因为相同元素的相对位置不变,如果两个元素相同,插入元素放在相同元素后面。
void insertionSort(int array[], int len) {
if (len<2) {
return 0;
}
for (int i=1; i=0 && insertValue
public static void insertionSort(int[] array) {
if (null==array || 1==array.length) {
return;
}
for (int i=1; i=0 && insertValue
def InsertionSort(arr):
n = len(arr)
for i in range(1,n):
temp = arr[i]
# j保存元素temp应该插入的位置
for j in range(i,-1,-1):
if arr[j-1]>temp and j>0:
arr[j] = arr[j-1] # 后移即可
else:
break
arr[j] = temp
插入排序标准算法在有序区查找合适位置时,采用从左到右逐一查找的方法。
我们可以采用二分查找的方法来进一步优化。