插入排序算法的分析
Insertion-Sort需要的时间依赖于输入规模和被排序程度。输入规模的最佳概念依赖于研究的问题。对许多问题,如排序或计算离散傅里叶变化,最自然的量度是输入中的项数。再如两个整数相乘,输入规模的最佳量度是用通常二进制记号表示输入所需的总位数。有时,用两个数而不是一个数来描述输入规模更合适,如某个算法输入的是一个图,则输入规模可以用该图中的顶点数和边数来描述。
一个算法在特定输入上的运行时间是值执行的基本操作或步数。首先给出Insertion-Sort中,每条语句的执行时间和执行次数。对
j
=
2
,
3
,
⋯
 
,
n
,
j=2,3,\cdots,n,
j=2,3,⋯,n, 其中
n
=
A
.
l
e
n
g
t
h
n=A.length
n=A.length,假设
t
j
t_j
tj表示对那个值
j
j
j第4行执行while
循环测试的次数,当一个for
或while
循环按通常的方式(即由于循环头中的测试)退出时,执行测试的次数比执行循环提的次数多1。
for j = 2 to A.length
key = A[j]
i = j - 1
while(i > 0 and A[i] > key)
A[i + 1] = A[i]
i = i - 1
A[i + 1] = key
Insertion-Sort | 代价 | 次数 |
---|---|---|
for j = 2 to A.length | c 1 c_1 c1 | n n n |
key = A[j] | c 2 c_2 c2 | n − 1 n-1 n−1 |
i = j - 1 | c 3 c_3 c3 | n − 1 n-1 n−1 |
while(i > 0 and A[i] > key) | c 4 c_4 c4 | ∑ j = 2 n t j \sum\limits^n_{j=2}t_j j=2∑ntj |
A[i + 1] = A[i] | c 5 c_5 c5 | ∑ j = 2 n ( t j − 1 ) \sum\limits^n_{j=2}(t_j-1) j=2∑n(tj−1) |
i = i - 1 | c 6 c_6 c6 | ∑ j = 2 n ( t j − 1 ) \sum\limits^n_{j=2}(t_j-1) j=2∑n(tj−1) |
A[i + 1] = key | c 7 c_7 c7 | n − 1 n-1 n−1 |
为计算在具有
n
n
n个值的输入上Insertion-Srot的运行时间
T
(
n
)
T(n)
T(n),我们将代价和次数对应元素之积求和,得:
T
(
n
)
=
c
1
n
+
c
2
(
n
−
1
)
+
c
3
(
n
−
1
)
+
c
4
∑
j
=
2
n
t
j
+
c
5
∑
j
=
2
n
(
t
j
−
1
)
+
c
6
∑
j
=
2
n
(
t
j
−
1
)
+
c
7
(
n
−
1
)
T(n) = c_1n + c_2(n-1) + c_3(n-1) + c_4\sum^n_{j=2}t_j + c_5\sum^n_{j=2}(t_j - 1) + c_6\sum^n_{j=2}(t_j - 1) + c_7(n-1)
T(n)=c1n+c2(n−1)+c3(n−1)+c4j=2∑ntj+c5j=2∑n(tj−1)+c6j=2∑n(tj−1)+c7(n−1)
- 最佳运行时间:当输入数组已经排序时,对于每个$j=2, 3,\cdots, n,
我
们
发
现
在
第
4
行
,
当
我们发现在第4行,当
我们发现在第4行,当i
取
初
值
取初值
取初值i-1
时
,
有
时,有
时,有A[i]\le key$,则出现最佳情况。运行时间为:
T ( n ) = c 1 n + c 2 ( n − 1 ) + c 3 ( n − 1 ) + c 4 ( n − 1 ) + c 7 ( n − 1 ) = ( c 1 + c 2 + c 3 + c 4 + c 7 ) n − ( c 2 + c 3 + c 4 + c 7 ) T(n) = c_1n + c_2(n-1) + c_3(n-1) + c_4(n-1) + c_7(n-1) \\ = (c_1 + c_2 + c_3 + c_4 + c_7)n - (c_2 + c_3 + c_4 + c_7) T(n)=c1n+c2(n−1)+c3(n−1)+c4(n−1)+c7(n−1)=(c1+c2+c3+c4+c7)n−(c2+c3+c4+c7)
我们把该运行时间表示为 a n + b an+b an+b,其中常量 a a a和 b b b依赖于语句代价 c i c_i ci。因此,它是 n n n的线性函数。
- 最坏运行时间:当输入的数组为反序时,对于每个
j
=
2
,
3
,
⋯
 
,
n
,
j=2, 3, \cdots, n,
j=2,3,⋯,n,,有
t
j
=
j
t_j = j
tj=j。
∑ j = 2 n j = n ( n + 1 ) 2 − 1 \sum^n_{j=2}j = \dfrac{n(n+1)}{2} - 1 j=2∑nj=2n(n+1)−1
和
∑ j = 2 n ( j − 1 ) = n ( n − 1 ) 2 \sum^n_{j=2}(j-1) = \dfrac{n(n-1)}{2} j=2∑n(j−1)=2n(n−1)
则运行时间为:
T
(
n
)
=
c
1
n
+
c
2
(
n
−
1
)
+
c
3
(
n
−
1
)
+
c
4
(
n
(
n
+
1
)
2
−
1
)
+
c
5
(
n
(
n
−
1
)
2
)
+
c
6
(
n
(
n
−
1
)
2
)
+
c
7
(
n
−
1
)
=
1
2
(
c
4
+
c
5
+
c
6
)
n
2
+
(
c
1
+
c
2
+
c
3
+
c
4
2
−
c
5
2
−
c
6
2
+
c
7
)
n
−
(
c
2
+
c
3
+
c
4
+
c
7
)
T(n) = c_1n + c_2(n-1) + c_3(n-1) + c_4(\dfrac{n(n+1)}{2} - 1) + c_5(\dfrac{n(n-1)}{2}) + c_6(\dfrac{n(n-1)}{2}) +c_7(n-1)\\ = \dfrac{1}{2}(c_4 + c_5 + c_6)n^2 + (c_1 + c_2 + c_3 + \dfrac{c_4}{2} - \dfrac{c_5}{2} - \dfrac{c_6}{2} + c_7)n - (c_2 + c_3 + c_4 + c_7)
T(n)=c1n+c2(n−1)+c3(n−1)+c4(2n(n+1)−1)+c5(2n(n−1))+c6(2n(n−1))+c7(n−1)=21(c4+c5+c6)n2+(c1+c2+c3+2c4−2c5−2c6+c7)n−(c2+c3+c4+c7)
我们把该运行时间表示为 a n 2 + b n + c an^2 + bn + c an2+bn+c,其中常量 a a a、 b b b和 c c c依赖于语句代价 c i c_i ci,它是 n n n的二次函数。
最坏情况和平均情况分析
最坏情况运行时间的重要性:
- 一个算法的最坏情况运行时间给出了任何输入的运行时间的一个上界。
- 对某些算法,最坏情况经常出现。
- 平均情况往往与最坏情况一样差。
增长量级
我们使用简化抽象使Insertion-Sort的分析更加容易。首先,通过使用常量 c i c_i ci表示这些代价来忽略每条语句的实际代价。其次,注意到这些常量也提供了比我们真正需要的要多的细节:把最坏情况运行时间表示为 a n 2 + b n + c an^2+bn+c an2+bn+c,其中常量 a a a、 b b b和 c c c依赖于语句代价 c i c_i ci。
现在,我们关注算法运行时间的增长率或增长量级,所以我们只考虑公式中最高阶项,因为当 n n n的值很大时,低阶项相对来说不太重要,同时也忽略最高阶项的常系数。我们记插入排序的最坏运行时间为 θ ( n 2 ) \theta(n^2) θ(n2)。
如果一个算法的最坏情况运行时间具有比另一个算法更低的增长量级,那么我们通常认为前者比后者更有效。
练习
2.2-1
θ ( n 3 ) = θ ( n 3 1000 − 100 n 2 − 100 n + 3 ) \theta(n^3) = \theta(\dfrac{n^3}{1000}-100n^2-100n+3) θ(n3)=θ(1000n3−100n2−100n+3)
2.2-2
循环不变式 :对于每次循环,子数组中(已选择的元素)已排序。
初始化 :在第一次循环前
j
=
1
j = 1
j=1时,子数组中不包含元素,故是已排序的。
保持 :从
A
[
j
]
⋯
A
[
n
]
A[j] \cdots A[n]
A[j]⋯A[n]中选出最小数与
A
[
j
]
A[j]
A[j]交换,因此
A
[
1
]
≤
A
[
2
]
≤
⋯
≤
A
[
j
−
1
]
≤
A
[
j
]
A[1] \le A[2] \le\cdots \le A[j - 1] \le A[j]
A[1]≤A[2]≤⋯≤A[j−1]≤A[j]。循环不变式保持。
结束 :当
j
=
n
−
1
j = n - 1
j=n−1 时,
A
[
n
−
1
]
A[n - 1]
A[n−1]必然小于
A
[
n
]
A[n]
A[n](由保持可得),故
A
[
0
]
⋯
A
[
n
]
A[0] \cdots A[n]
A[0]⋯A[n]都是有序的。算法成立
-
对前n-1数排序
由保持的性质可值,当 j = n − 1 j = n - 1 j=n−1 时 从 A [ n − 1 ] A[n - 1] A[n−1]和 A [ n ] A[n] A[n] 中选出最小数和 A [ n − 1 ] A[n - 1] A[n−1] 交换, 因此 A [ n − 1 ] ≤ A [ n ] A[n - 1] \le A[n] A[n−1]≤A[n]。故不需要对前n-1数执行即可。 -
最好情况和最坏情况
最好: θ ( n 2 ) \theta(n^2) θ(n2) 最坏: θ ( n 2 ) \theta(n^2) θ(n2)
# -*- coding:utf-8 -*-
import sys
import random
def findMin(array):
_min, index = sys.maxsize, 0
for j in range(len(array)):
if _min > array[j]:
_min, index = array[j], j
return _min, index
def selectSort(array):
for i in range(0, len(array) - 1):
_min, index = findMin(array[i:])
array[index + i], array[i] = array[i], array[index + i]
if __name__ == "__main__":
array = random.sample([x for x in range(100)], 20)
print("Before Select Sort:",array)
selectSort(array)
print("End Select Sort:",array)
2.2-3
-
平均情况 要检查 n + 1 2 \frac{n + 1}{2} 2n+1个元素,运行时间 θ ( n ) \theta(n) θ(n)
由于假定要查找的元素等可能为数组中的任意元素,所以每次查找得到结果的概率为: P ( A ) = 1 n P(A) = \frac{1}{n} P(A)=n1。假设进行n次实验,数组长度均为m,每次实验可以视为独立事件,则在数组 A n A_n An 中查找得到结果得概率为 P ( A i ) = i m P(A_i) = \frac{i}{m} P(Ai)=mi。则n次实验得总概率为根据概率的有限可加性可得:
P ( A 1 ∪ A 2 ∪ ⋯ ∪ A n ) = P ( A 1 ) + P ( A 2 ) + ⋯ + P ( A n ) = 1 n + 2 n + ⋯ + n n = 1 2 ( n + 1 ) P(A_1 \cup A_2 \cup \dots \cup A_n) = P(A_1) + P(A_2) + \dots + P(A_n)\\ = \dfrac{1}{n} + \dfrac{2}{n} + \cdots + \dfrac{n}{n}\\ = \dfrac{1}{2}(n + 1) P(A1∪A2∪⋯∪An)=P(A1)+P(A2)+⋯+P(An)=n1+n2+⋯+nn=21(n+1) -
最坏情况 要检查 n n n个元素,运行时间 θ ( n ) \theta(n) θ(n)
2.2-4
测试边界情况和最坏情况,尽可能的在每一步中使用 θ ( n ) \theta(n) θ(n)小的操作。