以下是本人学习极客时间的专栏《数据结构与算法之美》后,自己动手敲代码实现,并写下当时的思考,希望对你也有帮助。
系列文章:
工作后,为什么还要学习数据结构与算法
Python-排序-冒泡排序-优化
Python-排序-选择排序-优化
插入排序,我想你也并不陌生。可以简单地这样理解,插入排序就是就是往一个有序的数列中添中新的数据,插入之后保证数据列仍然有序,因此叫插入排序。
那么具体是如何实现的呢?要想保证插入后数据仍然有序,就需要先确定插入数据的位置。 首先我们将待排序的数据分为两个区间,有序区间和无序区间,初始的有序区间只包含一个元素,也就是数组的第一个元素,其他的就是无序区间;然后,依次从无序区间中选取一个元素,在有序区间找到合适的插入位置将其插入,并保证已排序区间的数据一直有序,最后重复这个过程,直到无序区间的元素为空,算法结束。
关键点:找到合适的位置插入前,需要先将插入位置后面的元素,按顺序往后移动,空出位置后再将新元素插入。
你可以先试着自己写写代码,练习 Python 编码的能力,不能眼高手低。下面是我写的未优化的插入排序算法
##未优化版插入排序
#encoding=utf-8
def insert_sort(data_list):
'''
无优化版
'''
count=0 #统计循环次数
length = len(data_list)
for i in range(1,length ): #默认第一个位置的元素是已排序区间,因此下标从 1 开始
tmp = data_list[i] #待插入的数据
j = i
while j > 0: #从已排序区间查找插入位置
count +=1
if tmp < data_list[j-1]:
data_list[j] = data_list[j-1] #元素向后移动,腾出插入位置
else:
break
j -= 1
data_list[j] = tmp #插入操作
print(data_list)
print(f"总循环次数为 {count}")
return data_list
上述代码中的 count 只是为了统计循环次数,目的是和优化版的进行对比,当然您也可以对时间复杂度进行分析来对比性能的差异。 print(data_list) 是为了打印出每一次插入后数据列的结果,您可以对比结果来理解插入排序算法。
我们先找一组数据试跑下:
if __name__ == "__main__":
unsort = [1,3,4,2,1,5,6,7,8,4]
print(*insert_sort(unsort))
执行结果如下所示:
[1, 3, 4, 2, 1, 5, 6, 7, 8, 4]
[1, 3, 4, 2, 1, 5, 6, 7, 8, 4]
[1, 2, 3, 4, 1, 5, 6, 7, 8, 4]
[1, 1, 2, 3, 4, 5, 6, 7, 8, 4]
[1, 1, 2, 3, 4, 5, 6, 7, 8, 4]
[1, 1, 2, 3, 4, 5, 6, 7, 8, 4]
[1, 1, 2, 3, 4, 5, 6, 7, 8, 4]
[1, 1, 2, 3, 4, 5, 6, 7, 8, 4]
[1, 1, 2, 3, 4, 4, 5, 6, 7, 8]
总循环次数为 18
1 1 2 3 4 4 5 6 7 8
###性能分析
- 空间复杂度:除了运行时需要一个临时变量存储交换的数据和下标,不需要额外的存储空间,因此空间复杂度是O(1),是原地排序算法。
- 稳定性:对于值相同的元素,我们可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保证原有的前后顺序不变,因此是一种稳定的排序算法。
- 时间复杂度:如果数据是有序的,我们不需要搬移任何数据,在查找插入位置时,从尾到头在有序区间查找插入位置,每次只需要比较一次即可确定插入位置,因此最好的时间复杂度为O(n)。如果数据是倒序的,每次都相当于在数据的第一个位置插入新数据,所以需要移动大量的数据,最坏时间复杂度为O(n^2)。平时时间复杂度,由于数据中插入一个元素的平均时间复杂度为O(n),因此对于插入排序来说,每次插入操作都相当于在数组中插入一个数据,循环执行 n 次插入操作,所以平均时间复杂度为O(n^2)。
优化入口
当有序区间数据量很大时,查找数据的插入位置就会显得非常耗时,插入排序算法每次都是从有序区间查找插入位置,以此为切入点,我们可以使用二分查找法来快速确认待插入的位置,于是就有了优化版的插入排序算法,也叫二分查找插入算法。
##优化版插入排序
def insert_sort2(data_list):
'''
使用二分查找函数确定待插入元素在有序区间的插入位置
'''
count=0 #统计循环次数
length =