【排序算法 6】简单插入排序
简单插入排序
先吐槽一下最近忙成狗的日程,搞得都没心情写文。
【算法介绍】
简单插入排序也称作直接插入排序是利用每个数字从后向前插入到对应位置的方式,使得最终数列有序。其图形化表示如下:
简单插入排序演示
不难看出,按照从左到右的顺序每次选取一个新数,将新数插入到左边有序的数列中,过程中需要不断比较新数与左边数字的大小,并进行数字位置的移动。
【代码实现】
注意到每一趟插入排序可以使左边有序的区间增加1,所以设计双重循环:
第一重循环:从第2个数字开始向后至第n个数字,枚举每次向前插入的新数(第1个数无需排序),
第二重循环:从第一重循环枚举到的数字开始,向前插入到合适的位置。
a = [16,12,8,13,7,5]
n = len(a)
for i in range(1, n): #从第1到n-1(注意数列位置从0开始)
tmp = a[i] #将新数存储下来
j = i-1 #从新数的上一个开始
while tmp<a[j] and j>-1: #若比上一个数字小且没有越界
a[j+1] = a[j] #将上一个数字下移
j -= 1 #继续找更前面的位置
a[j+1] = tmp #跳出时刚好j+1就是插入的位置
print(a)
【效率分析】
可以注意到,简单插入排序的双重循环,在最坏情况下,将是n^2级别的循环。
想象一个完全降序的数列:[5,4,3,2,1],将其进行升序排序,则排序时从左到右对每个新数都要插入到最左边的位置,交换次数为 4+3+2+1 = 10,若数列为n个降序的数字,如[n,n-1,n-2……,2,1],则交换次数为1+2+3……+n-1 = n*(n-1)/2,效率和冒泡还有选择排序有得一拼。
但也有好的时候,例如数列[1,2,3,5,4],则只需1次交换,所以当数列越接近有序,简单插入排序的效率越能体现。
但是大多数时候排序的数据是随机的,且数据量还是较大的,比起高效排序算法,简单插入排序就没什么用武之地了。
【拓展】逆序对
在排序中有一个非常重要的概念:逆序对。
设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
说白了,对于数列[2,3,1],2、3保持升序,我们认为是有序的,而2、1呈降序,我们认为是“逆序的”,不难看出,共有[2,1]、[3,1]两对逆序对。
逆序对的个数体现了数列的有序程度,是排序问题中经常出现的概念,合理使用甚至可以提供新的解题思路。逆序对越多,则数列越无序,否则数列越接近有序,当逆序对为0时,数列完全有序。
逆序对的统计方法有很多,例如冒泡排序的交换次数其实就是逆序对的个数,简单插入排序的过程中也容易统计逆序对。而高效做法有归并排序统计逆序对、数据结构统计逆序对等。
【小结】
简单插入排序思路易于理解,对于新手而言唯一的难点在于双重循环时对位置的把控,这需要自己利用草稿整理图形化的过程,有时还需要代入具体数值进行推敲才能熟悉其代码过程。
简单插入排序虽然效率不高,但是在其思想基础上进行优化后有一个高效版本,也就是希尔排序。