2.1.目标
- 了解为何算法分析的重要性
- 能够用大“O”表示法来描述算法执行时间
- 了解在 Python 列表和字典类型中通用操作用大“O”表示法表示的执行时间
- 了解 Python 数据类型的具体实现对算法分析的影响
- 了解如何对简单的 Python 程序进行执行时间检测
2.2.什么是算法分析
算法分析主要就是从计算资源的消耗的角度来评判和比较算法。我们想要分析两种算法并且指出哪种更好,主要考虑的是哪一种可以更高效地利用计算资源。或者占用更少的资源。
从这点上看,思考我们通过计算资源这个概念真正想表达的是什么是非常重要的。有两种方法来看待它。一种是考虑算法解决问题过程中需要的存储空间或内存。
作为空间需求的一个可替代物,我们可以用算法执行所需时间来分析和比较算法。这种方法有时被称为算法的“执行时间”或“运行时间”。
在 Python 中,我们可以考虑我们使用的系统通过标定开始时间和结束时间来作为标准衡量一个函数。在 time 模块有一个叫 time 的函数,它能返回在某些任意起点以秒为单位的系统当前时间。通过在开始和结束时两次调用这个函数,然后计算两次时间之差,我们就可以得到精确到秒(大多数情况为分数)的运行时间。
直观上,我们可以看到迭代算法似乎做了更多的工作,因为一些程序步骤被重复执行,这可能就是它需要更长时间的原因。此外,迭代算法所需要的运行时间似乎会随着 n 值的增大而增大。
2.3 大“O”表示法
如果把每一小步看作一个基本计量单位,那么一个算法的执行时间就可以表达为它解决一个问题所需的步骤数。
数量级函数用来描述当规模 n 增加时,T(n)函数中增长最快的部分。这种数量级函数一般被称为大“O”表示法,记作 O(f(n))。它提供了计算过程中实际步数的近似值。函数 f(n)是原始函数 T(n)中主导部分的简化表示。
尽管前面求和函数的例子没有体现,但我们还是注意到有时算法的运行时间还取决于具体数据而不仅仅是问题规模。对于这种算法,我们把它们的执行情况分为最好的情况,最坏的情况和平均情况。某个特定的数据集会使算法程序执行情况极差,这就是最坏的情况。然而另一个不同的数据集却能使这个算法程序执行情况极佳。不过,大多数情况下,算法程序的执行情况都介于这两种极端情况之间,也就是平均情况。
常见函数的大“O”表示法:
小试牛刀
编写两个 Python 函数来寻找一个列表中的最小值。函数一将列表中的每个数都与其他数作比较,数量级是 O(n²).函数二的数量级是 O(n)。
import time
from random import randrange
alist = []
def find_min(alist):
#假设alist中的第一个数字最小
my_min = alist[0]
#遍历alist
for i in alist:
#最小标记flag指示i是否为alist最小,初始为True
flag = True
#遍历alist
for j in alist:
#如果i>j(存在比i小的数)
if i >j:
#标记flag为false
flag = False
#当i为alist最小,my_min=i
if flag:
my_min = i
return my_min
def findMin(alist):
#初始化当前最小值minsofar=alist[0]
minsofar = alist[0]
#遍历alist
for i in alist:
#如果i小于minsofar
if i < minsofar:
minsofar = i
return minsofar
#当list长度为100,000的时候,算法一等了1分多钟没算出来...
#当list长度为100,000*100的时候,算法二只要0.2s...
#当list长度为100,000*1000的时候,算法二python占用了3.2Gb内存,CPU占用30%,等了半分钟,也算不出来了...
listSize = 100000000
'''
alist = [randrange(10000000) for i in range(listSize)]
start = time.time()
print(find_min(alist))
end = time.time()
print('验证:%d'%min(alist))
print("listlength:{0},time:{1},O(n)=n^2".format(listSize,end-start))
print('---------------------------------------------------------------')
'''
alist = [randrange(10000000) for i in range(listSize)]
start = time.time()
print(findMin(alist))
end = time.time()
print('验证:%d'%min(alist))
print("listlength:%d,time:%.30f,O(n)=n"% (listSize,end-start))
print('-------------------ENDENDENDENDENDENDEND-------------------------')
2.4.一个乱序字符串检查的例子
显示不同量级的算法的一个很好的例子是字符串的乱序检查。乱序字符串是指一个字符串只是另一个字符串的重新排列。
为了简单起见,我们假设所讨论的两个字符串具有相等的长度,并且他们由 26 个小写字母集合组成。我们的目标是写一个布尔函数,它将两个字符串做参数并返回它们是不是乱序。
2.4.1.解法1:检查
乱序字符串问题的第一种解法是检查第一个字符串中的所有字符是不是都在第二个字符串中出现。如果能够把每一个字符都“检查标记”一遍,那么这两个字符串就互为乱序字符串。检查标记一个字符要用特定值 None 来代替,作为标记。然而,由于字符串不可变,首先要把第二个字符串转化成一个列表。第一个字符串中的每一个字符都可以在列表的字符中去检查,如果找到,就用 None 代替以示标记。
def anagramSolution1(s1,s2):
#将字符串s2转换为一个list
alist = list(s2)
#初始化s1的指针pos1=0
pos1 = 0
#是否在s2字符串中找到s1字符串中的【所有字符】的标记stillOK
stillOK = True
#当指针pos1还未移动到s1结尾,且在遍历s2时能找到s1中的字符
while pos1 < len(s1) and stillOK:
#初始化s2的指针pos2=0
pos2 = 0
#是否在s2字符串中找到s1字符串中的【单个字符】的标记found
found = False
while pos2 < len(alist) and not found:
#如果在遍历s2字符串的时候找到了s1中的字符,则found=true
if s1[pos1] == alist[pos2]:
found = True
else:
pos2 = pos2 + 1
#如果找到,将在s2中找到且存在于s1中的字符时,将s2中的字符变为None
if found:
alist[pos2] = None
#如果没找到,stillOK=false
else:
stillOK = False
#s1中的位置指针pos1移动到下一个位置
pos1 = pos1 + 1
return stillOK
print(anagramSolution1('abcd','acfdffffb'))
为了分析这个算法,我们要注意到 s1 中 n 个字符的每一个都会引起一个最多迭代到 s2 列表中第n 个字符的循环。(考虑最坏的情况)列表中的 n 个位置各会被寻找一次去匹配 s1 中的某个字符,那么执行总数就是从 1 到 n 的代数和。我们之前提到过它可以这样表示: