插入排序,是最经典的排序算法。下面从以下几个方面介绍该算法。
- 算法原理[algorithm principle]
- 算法实现[algorithm implementation]
- 时间复杂度[time complexity]
- 空间复杂度[space complexity]
- 稳定性[stability]
- 算法优化-(监视哨)
1.算法原理
将无序元素,分别插入到有序系列合适位置:
- step1:在一个序列划分为【 有序区域:无序序列 】
- step2 :在无序区域中选择一个元素,插入到有序区域合适的位置中
- step3: 循环step2 ,直到只剩下一个元素,排序结束。
1.1 图解算法
假设a=[1,3,2,9,7,2],下面对a进行升序排列。
1.1.1 进行首次循环
到此首次循环结束:
代码描述:
temp=取出元素3
循环 只要 当前元素值大于temp 并且 当前坐标>=0
a[当前坐标+1]=a[当前坐标]
当前坐标=当前坐标-1
循环结束
a[当前坐标+1]=temp
1.1.2进行第二次循环
1.1.3进行第三次循环
1.1.4进行第四次循环
1.1.5进行第五次循环
到此排序结束
代码描述:
外部循环:从1号元素到(长度-1)号元素
temp=取出元素3
循环 只要 当前元素值大于temp 并且 当前坐标>=0
a[当前坐标+1]=a[当前坐标]
当前坐标=当前坐标-1
循环结束
a[当前坐标+1]=temp
循环结束
排序结束
2.代码实现:
使用语言-python
def insertsort(nums):
length=len(nums)#获得数组长度
for i in range(1,length):#从1号元素到(长度-1)号元素
temp=nums[i]#temp=取出元素
j=i-1#记录内部循环开始坐标
while j>=0 and nums[j]>temp:# 当前元素值大于temp 并且 当前坐标>=0
nums[j+1]=nums[j]#a[当前坐标+1]=a[当前坐标]
j-=1#当前坐标=当前坐标-1
nums[j+1]=temp#a[当前坐标+1]=temp
print(nums)#输出排序后的数组
3.时间复杂度
每次循环,插入到合适位置
同时,每次循环都会复制值给后面一个元素。
- 最坏情况 每次进行i-1次赋值(每次都是插入到开始位置)
- 最好情况,每次进行1次赋值(每次都是插入当前位置)
3.1 最好情况
此时,数组初始顺序为正序,如[1,2,3,4]。
外部循环为n-1次,每次循环进行1次复制 ,则共执行操作数=n-1 次,O( (n-1) )故时间复杂度如公式(3-1)所示。
O((n−1))=O(n)(3-1) O( (n-1) )=O( n) \tag {3-1}O((n−1))=O(n)(3-1)
3.2最坏情况
此时,数组初始顺序为逆序,如[4,3,2,1]。
首次循环赋值n-1次 ,二次循环 赋值n-2 次,以此类推,有通项公式 如公式(3-2)所示、时间复杂度如公式(3-3)所示。
T=n−1+n−2+n−3+...+1+12∗(n−1)=n(n−1+1)+(n−1)2(3-2)T=n-1+n-2+n-3+...+1+\frac {1}{2}*(n-1)=\frac {n(n-1+1)+(n-1)}{2} \tag {3-2}T=n−1+n−2+n−3+...+1+21∗(n−1)=2n(n−1+1)+(n−1)(3-2)
O(T)=O(12(n2+n−1))≈O(n2)(3-3)O(T)=O(\frac{1}{2} (n^2+n-1))≈O(n^2) \tag {3-3}O(T)=O(21(n2+n−1))≈O(n2)(3-3)
3.3 复杂度总结
综上所述,选择排序的时间复杂度如下表所示。
状态 | 最好情况 | 最坏情况 | 平均时间复杂度 |
---|---|---|---|
时间复杂度 | O(n)\ O(n) O(n) | O(n2)\ O(n^2) O(n2) | O(n2)\ O(n^2) O(n2) |
4.空间复杂度
选择排序是原地排序的一种排序,所以空间复杂度为O(1)
原地排序就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
5.稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。–百度百科
在上述的图表案例中,如果交换的条件: 只有 当前元素值大于temp 并且 当前坐标>=0,所以相同值的元素,可以保持相对位置。
6.算法优化
算法优化是指对算法的有关性能进行优化,如时间复杂度、空间复杂度、正确性、健壮性。由于算法应用情景变化很大,算法优化可以使算法具有更好泛化能力。
6.1 分析问题
优化的出发点是:循环的结束条件,如公式(6-1)所示:
j>=0 And nums[j]>temp:(6-1) j>=0 ~~~~~~~And ~~~~~~ nums[j]>temp: \tag {6-1}j>=0 And nums[j]>temp:(6-1)
每次循环都要判断是否到达数组边界,但这并非是必要的。
6.2 解决方案:
使用数组的初始位置记录temp值(设置监视哨),如下图所示
这无需判断边界,只要判断nums[j]>temp 即可,但是代价是需要一个数组空间。
代码实现
# insertsort.py
#原始算法
def insertsort(nums):
length=len(nums)
for i in range(1,length):
temp=nums[i]
j=i-1
while j>=0 and nums[j]>temp:
nums[j+1]=nums[j]
j-=1
nums[j+1]=temp
# print(nums)
#优化算法
def insertsort2(nums):
length=len(nums)
for i in range(2,length):
nums[0]=nums[i]
j=i-1
while nums[j]>nums[0]:
nums[j+1]=nums[j]
j-=1
nums[j+1]=nums[0]
# print(nums[1:])
nums=[0,3, 2, 1, 8, 5, 4, 6]
import time
start=time.clock()
for i in range(100000):
insertsort(nums[1:])
print(f"原始执行100000次 用时{time.clock()-start}")
start=time.clock()
for i in range(100000):
insertsort2(nums)
print(f"优化后执行100000次 用时{time.clock()-start}")
执行结果:
原始执行100000次 用时0.279714
优化后执行100000次 用时0.173260
[Finished in 0.5s]
由此可见,优化后,排序的执行速度有了明显提高。