基础算法:插入排序(python实现)
算法原理
将数组看作左右两个部分,保持数组左边有序,将右边的每个数字依次插入到左边的相应位置。
算法实现
def insertion_sort(unsorted_array):
for i in range(len(unsorted_array)):
for j in range(i, 0, -1):
if unsorted_array[j-1] > unsorted_array[j]:
unsorted_array[j-1], unsorted_array[j] = unsorted_array[j], unsorted_array[j-1]
else:
break
上述实现中,对数组第i位的元素不断向前交换,直到无法继续向前交换,此时第i位之前的数组有序。
算法优化
上述算法实现中进行交换操作导致开销很大。其实可以使用右移操作代替交换操作,首先记下第i位的值,将左边的数组逐个右移,直到找到合适的位置,将之前记下的值放入即可。
def insertion_sort_optimization(unsorted_array):
for i in range(len(unsorted_array)):
tmp = unsorted_array[i] #记下第i位的值
is_arranged = False
for j in range(i, 0, -1):
if unsorted_array[j-1] > tmp:
unsorted_array[j] = unsorted_array[j-1] #右移
else:
is_arranged = True
unsorted_array[j] = tmp #把值放入合适的位置
break
if not is_arranged:
unsorted_array[0] = tmp
由于内层循环序列range(i,0,-1)
的最小值为1,导致无法把数字放到数组的首位,因此需要进行额外的判断,如果取出的数字在完成内层循环后没有被放入任何位置,说明只能放在数组首位。
上述优化通过进行位置移动来代替交换操作,有效降低了赋值语句的执行次数。
算法测试
对插入排序算法及同为$ O(N^2) $复杂度的选择排序算法进行了正确性及性能测试,以下是测试结果。
创建测试数组
对于插入排序:
1000大小数组耗时:0.047142s
10000大小数组耗时:4.579244s
1000大小随机范围较小数组耗时:0.030886s
1000大小近乎有序数组耗时:0.009190s
.
对于优化后的插入排序:
1000大小数组耗时:0.035747s
10000大小数组耗时:3.236195s
1000大小随机范围较小数组耗时:0.023929s
1000大小近乎有序数组耗时:0.007998s
.
对于选择排序:
1000大小数组耗时:0.036213s
10000大小数组耗时:3.242478s
1000大小随机范围较小数组耗时:0.030851s
1000大小近乎有序数组耗时:0.027903s
.
对于优化后的选择排序:
1000大小数组耗时:0.029559s
10000大小数组耗时:2.578856s
1000大小随机范围较小数组耗时:0.023379s
1000大小近乎有序数组耗时:0.023745s
.
----------------------------------------------------------------------
Ran 4 tests in 14.045s
OK
将上述数据制成表格:
数据量 | 特点 | 插入排序 | 优化后的插入排序 | 选择排序 | 优化后的选择排序 |
---|---|---|---|---|---|
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) $。