什么是算法分析?

计算资源指标:一种是算法解决问题过程中需要的储存空间或内存,另一种是算法的执行时间

运行时间检测
time模块,获取计算机系统当前时间

例如:
方法一:累计求和程序的运行时间检测

import time

def sumOfN2(n):
	start = time.time()
	theSum = 0
	for i in range(1, n + 1):
		theSum = theSum + i
	end = time.time()
	return theSum, end - start

for i in range(5):
	print("Sum is %d required %10.7f seconds"% sumOfN2(10000))

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_字符串


Error:第一次输入没有在def的函数名括号里填n

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_python_02


后来搜索明白,括号里必须有一个占位符,专业的说是在类的函数内引入变量

方法二:无迭代的累计算法(利用求和公式)

#只改def这一段
def sunOfN3(n):
	start = time.time()
	theSum = (n*(n+1))/2
	end = time.time()
	return theSum, end - start

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_算法_03


新算法运行时间几乎与需要累计的数目无关

“大O表示法”

大O表示法:表示了所有上限中最小的那个上限
数量级函数Order of Magnitude f(n)

例如:
代码赋值语句可以分为4个部分
算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_运行时间_04
仅保留最高阶项n^2,去掉所有系数,数量级算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_变位词_05

a = 5
b = 6
c = 10
for i in range(n):
	for j in range(n):
		x = i * i
		y = j * j
		z = i * j
	for k in range(n):
		w = a * k + 45
		v = b * b
	d = 33

“变位词”判断

"变位词"判断问题

问题描述:所谓"变位词"是指两个词之间存在组成字母的重新排列关系,如:heart和earth

解题目标:写一个bool函数,以两个词作为参数,返回这两个词是否变位词

解法1:逐字检查
将词1中的字符逐个到词2中检查是否存在
存在就“打勾”标记
如果每个字符都能找到,则两个词就是变位词
只要有1个字符找不到,就不是变位词
为了简单,假设参与判断的两个词仅由大小写字母构成,且长度相等

def anagramSolution1(s1,s2):
    alist = list(s2)#复制s2到列表
    pos1 = 0
    stillOK =True
    while pos1 < len(s1) and stillOK:#循环s1的每个字符
        pos2 = 0
        found = False
        while pos2 < len(alist) and not found:
            if s1[pos1] == alist[pos2]:#在s2逐个对比
                found = True
            else:
                pos2 = pos2 + 1
        if found:
            alist[pos2] = None#找到,打勾
        else:
            stillOK = False#未找到,失败
        pos1 = pos1 + 1
    return stillOK
    
print(anagramSolution1('abcd','dcba'))

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_运行时间_06


这段值得学习的是 stillOK=True 增加了代码的可读性

实现“打勾”标记:将词2对应字符设为None

注意:字符串是不可变类型,需要先复制到列表中,如果没有复制会报错

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_运行时间_07


总执行次数n+n-1+n-2+…+3+2+1

数量级为O(n^2)

解法2:排序比较
将两个字符串都按照字母顺序排好序
再逐个字符对比是否相同,如果相同则是变位词

def anagramSolution2(s1,s2):
    alist1 = list(s1)
    alist2 = list(s2)#转为列表
    
    alist1.sort()
    alist2.sort()#分别排序
    pos = 0
    matches = True
    while pos < len(s1) and matches:
        if alist1[pos] == alist2[pos]:#逐个对比
            pos = pos + 1
        else:
            matches = False
        return matches
        
print(anagramSolution2('abcde','edcba'))

本算法时间主导的步骤是排序步骤
后面的学习将会知道排序算法的时间数量级差不多是O(n^2)或者O(n log n),大过O(n)

解法3:暴力法
穷尽所有可能的组合n!个
将s1中出现的字符进行全排列,再查看s2是否出现在全排列列表中

解法4:计数比较
对比两个词中每个字母出现的次数,如果26个字母出现的次数都相同的话,这两个字符串就一定是变位词

具体做法
为每个词设置一个26位的计数器,先检查每个词,在计数器中设定好每个字母出现的次数
计数完成后,进入比较阶段,看两个字符串的计数器是否相同,如果相同则输出是变位词的结论

def anagramSolution4(s1,s2):
    c1 = [0] * 26
    c2 = [0] * 26
    #分别都计数
    for i in range(len(s1)):
        pos = ord(s1[i])-ord('a')
        c1[pos] = c1[pos] + 1
    for i in range(len(s2)):
        pos = ord(s2[i])-ord('a')
        c2[pos] = c2[pos] + 1
    j = 0
    stillOK = True
    #计数器比较
    while j < 26 and stillOK:
        if c1[j] == c2[j]:
            j = j + 1
        else:
            stillOK = False
    return stillOK
print(anagramSolution4('apple','pleap'))

值得学习的:计数器的设置
总操作次数T(n)=2n+26,数量级O(n)
本算法依赖于两个长度为26的计数器列表,来保存字符计数,这需要更多的存储空间

时间空间之间的取舍和权衡

Python数据类型的性能

列表list和字典dict操作对比

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_运行时间_08


例如:

4种生成前n个整数列表的方法计时

#首先是循环连接列表(+)方式生成
def test1():
    l = []
    for i in range(1000):
        l = l + [i]
#然后是用append方法添加元素生成
def test2():
    l = []
    for i in range(1000):
        l.append(i)
#接着用列表推导式来做
def test3():
    l = [i for i in range(1000)]
#最后是range函数调用转成列表
def test4():
    l = list(range(1000))
from timeit import Timer
t1 = Timer("test1()","from __main__ import test1")
print("concat %f seconds\n" %t1.timeit(number=1000))

t2 = Timer("test2()","from __main__ import test2")
print("append %f seconds\n" % t2.timeit(number=1000))

t3 = Timer("test3()","from __main__ import test3")
print("comprehension %f seconds\n" % t3.timeit(number=1000))

t4 = Timer("test4()","from __main__ import test4")
print("list range %f seconds\n" % t4.timeit(number=1000))

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_变位词_09


列表连接(concat)最慢,List range最快,速度相差近200倍

是append两倍的样子

值得了解:timeit 创建一个Timer对象,指定需要反复运行的语句和需要运行一次的“安装语句”,然后调用这个对象的timeit方法,其中可以指定反复运行多少次

List基本操作的大O数量级

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_运行时间_10


例如:

list.pop的计时试验

从中部移除元素,要把移除元素后面的元素全部向前挪位复制一遍。这种实现方法能够保证列表按索引取值和赋值的操作很快,达到O(1)

import timeit
popzero = timeit.Timer("x.pop(0)","from __main__ import x")
popend = timeit.Timer("x.pop()","from __main__ import x")
print("pop(0)      pop()")
for i in range(1000000,100000001,1000000):
    x = list(range(i))
    pt = popend.timeit(number=1000)
    x = list(range(i))
    pz = popzero.timeit(number=1000)
    print("%15.5f, %15.5f"%(pz,pt))

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_python_11


将实验数据画成图表

pop()是平坦的常数,pop(0)

是线性增长的趋势

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_算法_12


dict数据类型

根据关键码(key)找到数据项,二列表是根据位置(index)

最常用的取值get和赋值set,contains(in)判断字典中是否存在某个关键码(key),性能为O(1)

例如,
list和dict的in操作对比

import timeit
import random
for i in range(10000,1000001,20000):
    t = timeit.Timer("random.randrange(%d) in x"%i,
                     "from __main__ import random,x")
    x = list(range(1))
    lst_time = t.timeit(number=1000)
    x = {j:None for j in range(i)}#数据是None,key来自于range(i)
    d_time = t.timeit(number=1000)
    print("%d,%10.3f,%10.3f"%(i,lst_time,d_time))

值得学习的:x = {j:None for j in range(i)}

算法设计与分析Python版PDF 王秋芬 算法设计与分析(python)_python_13


字典的执行时间与规模无关,是常数

列表的执行时间则随着列表的规模加大而线性上升