问题描述
【输入】n个数组成的序列 A n = [ a 1 , a 2 , ⋅ ⋅ ⋅ , a n ] A_n=[a_1,a_2,···,a_n] An=[a1,a2,⋅⋅⋅,an]
【输出】 A n A_n An的一个重新排列 A n ′ A_n' An′,使得 a 1 ≤ a 2 ≤ ⋅ ⋅ ⋅ ≤ a n a_1\le a_2\le ···\le a_n a1≤a2≤⋅⋅⋅≤an
算法展示
def insertion_sort(An):
"""插入排序"""
n = len(An) # 获取An的长度
for i in range(1, n):
key = An[i] # 当前需要插入排序的值
j = i - 1 # 左一位的下标
while j >= 0 and An[j] > key:
# 向左比较并移动,直到找到自己的位置
An[j + 1] = An[j]
j -= 1
An[j + 1] = key
return An
算法基本分析
加入一系列print
语句以辅助分析:
def insertion_sort(An):
"""插入排序"""
n = len(An) # 获取An的长度
print(f"输入:{An}\n")
for i in range(1, n):
key = An[i] # 当前需要插入排序的值
print(f"-----第{i}次-----")
print(f"origin:{An[0:i]} {An[i:]}")
j = i - 1 # 左一位的下标
while j >= 0 and An[j] > key:
# 向左比较并移动,直到找到自己的位置
An[j + 1] = An[j]
j -= 1
An[j + 1] = key
print(f"result:{An[0:i+1]} {An[i+1:]}")
print(f"\n输出:{An}")
return An
以[6,7,3,4,8,1,2,5]
作为输入的
A
n
A_n
An ,得到以下结果:
输入:[6, 7, 3, 4, 8, 1, 2, 5]
-----第1次-----
origin:[6] [7, 3, 4, 8, 1, 2, 5]
result:[6, 7] [3, 4, 8, 1, 2, 5]
-----第2次-----
origin:[6, 7] [3, 4, 8, 1, 2, 5]
result:[3, 6, 7] [4, 8, 1, 2, 5]
-----第3次-----
origin:[3, 6, 7] [4, 8, 1, 2, 5]
result:[3, 4, 6, 7] [8, 1, 2, 5]
-----第4次-----
origin:[3, 4, 6, 7] [8, 1, 2, 5]
result:[3, 4, 6, 7, 8] [1, 2, 5]
-----第5次-----
origin:[3, 4, 6, 7, 8] [1, 2, 5]
result:[1, 3, 4, 6, 7, 8] [2, 5]
-----第6次-----
origin:[1, 3, 4, 6, 7, 8] [2, 5]
result:[1, 2, 3, 4, 6, 7, 8] [5]
-----第7次-----
origin:[1, 2, 3, 4, 6, 7, 8] [5]
result:[1, 2, 3, 4, 5, 6, 7, 8] []
输出:[1, 2, 3, 4, 5, 6, 7, 8]
循环不变式(Loop Invariant)
循环不变式是在每一次循环中都不改变的性质。它有助于我们分析归纳类算法的正确性。
循环不变式的特点:
- 初始化时为真 (初始条件)
- 若单次循环开始时为真,则本次循环结束时也为真 (循环不变条件)
- 整个循环结束后,依旧为真 (结论)
可以对比数学归纳法:
- i=1时,结论成立 (初始条件)
- 若i=n时,结论成立,则i=n+1时,结论也成立 (归纳条件)
- 得证,对任意i,结论均成立 (结论)
对插入排序算法进行分析,
外层循环的循环不变式为
第
i
次循环开始时
,
左侧子序列
A
n
[
0
:
i
]
已排好序
,
即满足
a
1
<
a
2
<
⋅
⋅
⋅
<
a
i
第i次循环开始时,\\ 左侧子序列A_n[0:i]已排好序,\\ 即满足\ \ a_1<a_2<···<a_i
第i次循环开始时,左侧子序列An[0:i]已排好序,即满足 a1<a2<⋅⋅⋅<ai
- 初始化时, A n [ 0 : 1 ] A_n[0:1] An[0:1] 只有一个数,显然为真
- 假设第i次循环时,
A
n
[
0
:
i
]
A_n[0:i]
An[0:i]已排好序。此时
key=An[i]
。经过内层循环的比较与移动, A n [ 0 : i ] A_n[0:i] An[0:i]中大于key
的值都在key
的右侧,小于等于key
的值都在key
的左侧。即, A n [ 0 : i + 1 ] A_n[0:i+1] An[0:i+1]这i+1个数也已排好序。 - 得证,经过n-1次循环后, A n [ 0 : n ] A_n[0:n] An[0:n]这n个数已排好序。即,得到了排好序的 A n ′ A_n' An′ .
算法复杂度分析
输入规模(Input Size)
输入规模是指算法所接受的输入单位数据的个数。
不同问题的输入单位数据可能是不同的。
插入排序的输入单位数据是列表中的单个数字。
因此,其输入规模n为输入数字列表的元素个数。
运行时间(Running Time)
假设代码的每一步执行需要常数时间 c i c_i ci
记所有代码运行的时间和记为算法的运行时间 T n T_n Tn
def insertion_sort(An):
n = len(An) #----- c1 1
for i in range(1, n): #----- c2 n
key = An[i] #----- c3 n-1
j = i - 1 #----- c4 n-1
while j >= 0 and An[j] > key: #----- c5 sum_{i=1}^{n-1} t_i
An[j + 1] = An[j] #-----c6 sum_{i=1}^{n-1} (t_i-1)
j -= 1 #-----c7 sum_{i=1}^{n-1} (t_i-1)
An[j + 1] = key #-----c8 n-1
return An
如果为最佳情况,即输入的 A n A_n An已经排好了序,则 c 5 c_5 c5步骤只需要执行1次就能退出内层循环
则 ∑ i = 1 n − 1 t i = ∑ i = 1 n − 1 1 = n − 1 \displaystyle \sum_{i=1}^{n-1}t_i=\displaystyle \sum_{i=1}^{n-1}1=n-1 i=1∑n−1ti=i=1∑n−11=n−1 , ∑ i = 1 n − 1 ( t i − 1 ) = ∑ i = 1 n − 1 0 = 0 \displaystyle \sum_{i=1}^{n-1}(t_i-1)=\displaystyle \sum_{i=1}^{n-1}0=0 i=1∑n−1(ti−1)=i=1∑n−10=0
故执行时间
T ( n ) = c 1 + c 2 n + c 3 ( n − 1 ) + c 4 ( n − 1 ) + c 5 ( n − 1 ) + c 8 ( n − 1 ) T(n)=c_1+c_2n+c_3(n-1)+c_4(n-1)+c_5(n-1)+c_8(n-1) T(n)=c1+c2n+c3(n−1)+c4(n−1)+c5(n−1)+c8(n−1)
= ( c 2 + c 3 + c 4 + c 5 + c 8 ) n − ( c 3 + c 4 + c 5 + c 8 − c 1 ) =(c_2+c_3+c_4+c_5+c_8)n-(c_3+c_4+c_5+c_8-c_1) =(c2+c3+c4+c5+c8)n−(c3+c4+c5+c8−c1)
= A n + B =An+B =An+B
如果为最坏情况,即输入的 A n A_n An为逆序,则 c 5 c_5 c5步骤只需要执行(i+1)次才能退出内层循环
则 ∑ i = 1 n − 1 t i = ∑ i = 1 n − 1 ( i + 1 ) = ( 2 + n ) ( n − 1 ) 2 = n 2 + n − 2 2 \displaystyle \sum_{i=1}^{n-1}t_i=\displaystyle \sum_{i=1}^{n-1}(i+1)=\frac{(2+n)(n-1)}{2}=\frac{n^2+n-2}{2} i=1∑n−1ti=i=1∑n−1(i+1)=2(2+n)(n−1)=2n2+n−2 ,
∑ i = 1 n − 1 ( t i − 1 ) = ∑ i = 1 n − 1 i = n ( n − 1 ) 2 = n 2 − n 2 \displaystyle \sum_{i=1}^{n-1}(t_i-1)=\displaystyle \sum_{i=1}^{n-1}i=\frac{n(n-1)}{2}=\frac{n^2-n}{2} i=1∑n−1(ti−1)=i=1∑n−1i=2n(n−1)=2n2−n
故执行时间
T ( n ) = c 1 + c 2 n + c 3 ( n − 1 ) + c 4 ( n − 1 ) + c 5 ( n 2 + n − 2 2 ) + c 6 ( n 2 − n 2 ) + c 7 ( n 2 − n 2 ) + c 8 ( n − 1 ) \displaystyle T(n)=c_1+c_2n+c_3(n-1)+c_4(n-1)+c_5(\frac{n^2+n-2}{2})+c_6(\frac{n^2-n}{2})+c_7(\frac{n^2-n}{2})+c_8(n-1) T(n)=c1+c2n+c3(n−1)+c4(n−1)+c5(2n2+n−2)+c6(2n2−n)+c7(2n2−n)+c8(n−1)
= c 5 + c 6 + c 7 2 n 2 + ( c 5 − c 6 − c 7 2 + c 2 + c 3 + c 4 + c 8 ) n + ( c 1 − c 3 − c 4 − c 5 − c 8 ) \displaystyle =\frac{c_5+c_6+c_7}{2}n^2+(\frac{c_5-c_6-c_7}{2}+c_2+c_3+c_4+c_8)n+(c_1-c_3-c_4-c_5-c_8) =2c5+c6+c7n2+(2c5−c6−c7+c2+c3+c4+c8)n+(c1−c3−c4−c5−c8)
= A n 2 + B n + C =An^2+Bn+C =An2+Bn+C
设算法的执行时间为 T ( n ) T(n) T(n),则大O表示法---- O ( f ( n ) ) O(\ f(n)\ ) O( f(n) )代表 T ( n ) T(n) T(n)的上界的量级,大 Ω \Omega Ω 表示法---- Ω ( f ( n ) ) \Omega(\ f(n)\ ) Ω( f(n) )代表 T ( n ) T(n) T(n)的下界的量级。
如表示插入排序的执行时间: O ( n 2 ) O(\ n^2\ ) O( n2 ) , Ω ( n ) \Omega(\ n\ ) Ω( n )
常用大O表示法分析算法的时间复杂度。
由上述分析,
插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2).