• CS61A Efficiency 笔记


    CS61A Spring 学习笔记
    原文地址: http://composingprograms.com/pages/28-efficiency.html

    Measuring Efficiency

    计算一个程序需要多长时间和多少的内存是很困难的。一个可行的方式就是计算一个事件比如一个函数被调用了多少次。
    比如Fibonacci数列

    >>> def fib(n):
            if n == 0:
                return 0
            if n == 1:
                return 1
            return fib(n-2) + fib(n-1)

    比如我们计算fib(6)
    因为递归,所以每次计算都要重复调用更小的fib(x),导致很多冗余计算。
    定义一个计算某个函数被调用了多少次的高阶函数。这个函数的返回一个与输出等效函数,还包含一个计数用的变量。

    >>> def count(f):
            def counted(*args):
                counted.call_count += 1
                return f(*args)
            counted.call_count = 0
            return counted

    example 1:https://goo.gl/L6Ee3T

    def count(f):
        def counted(*args):
            counted.call_count += 1
            return f(*args)
        counted.call_count = 0
        return counted
    
    def test(n):
        if n<=3:
            return 1
        else: 
             return test(n-2)+test(n-4)+1
    
    test = count(test)
    ret = test(8)
    c = test.call_count

    example2:https://goo.gl/D8cxm5

    def count(f):
        def counted(*args):
            counted.call_count += 1
            return f(*args)
        counted.call_count = 0
        return counted
    
    def fib(n):
            if n == 0:
                return 0
            if n == 1:
                return 1
            return fib(n-2) + fib(n-1)
    
    fib = count(fib)
    ret = fib(6)
    c = fib.call_count

    Space

    想知道函数占用多少的空间。我们需要知道函数执行过程中如何使用,保留和回收内存。如果一个环境为正在执行的部分提供上下文,那么我们说这个环境是激活的。当函数最终调用了return,函数不再是激活的。
    在下面的执行过程中,当执行完成后,相应的环境(frame)不再需要时会变成灰色,表示不再激活。当return的返回值好没有被执行,对应的那个frame始终是激活的。
    这里定义了一个高阶函数去计算最多同时有多少个frame被激活。

    def fib(n):
        if n == 0:
            return 0
        if n == 1:
            return 1
        return fib(n-2) + fib(n-1)
    
    def count_frames(f):
            def counted(*args):
                counted.open_count += 1
                counted.max_count = max(counted.max_count, counted.open_count)
                result = f(*args)
                counted.open_count -= 1
                return result
            counted.open_count = 0
            counted.max_count = 0
            return counted
    
    fib = count_frames(fib)
    fib(4)

    https://goo.gl/vLsjiU

    Memoization

    在树回归中,像上面求解斐波那契数列的时候,很多次重复的计算是没有必要的。使用记忆化就能够把曾经计算过的结果用cache保存起来,在遇到相同的参数时,只需按照参数在cache中索引相应的值即可。这样做能够大大提高运算的效率。
    记忆化使用一个高阶函数来表达,它也可以作为一个装饰器(decorator)。

    >>> def memo(f):
            cache = {}
            def memoized(n):
                if n not in cache:
                    cache[n] = f(n)
                return cache[n]
            return memoized

    计算的过程由下图可以观察到
    fib_memo

    def fib(n):
        if n == 0:
            return 0
        if n == 1:
            return 1
        return fib(n-2) + fib(n-1)
    
    def count(f):
            def counted(*args):
                counted.call_count += 1
                return f(*args)
            counted.call_count = 0
            return counted 
    
    def memo(f):
        cache = {}
        def memoized(n):
            if n not in cache:
                cache[n] = f(n)
            return cache[n]
        return memoized       
    
    counted_fib = count(fib)
    fib  = memo(counted_fib)
    fib(5)
    
    counted_fib.call_count
    
    fib(6)
    
    counted_fib.call_count
    

    https://goo.gl/NQDRvj

    Orders of Growth

    影响函数执行时的space和time的因素很多,很难求得实际中使用了多少。为方便估算,把不同类型的计算按照不同的复杂度划分类别。
    使用Θ标注:
    n 表示输入的规模
    R(n)表示n为输入时需要的资源
    我们称R(n)Θ(f(n))的计算数量级增长(order of growth),写作R(n)=Θ(f(n)).
    存在一个上界和一个下界k1,k2,使得

    k1f(n)R(n)k2f(n)

    Example: Exponentiation

    使用递归计算 bn

    >>> def exp(b, n):
            if n == 0:
                return 1
            return b * exp(b, n-1)

    这是一个线性递归过程,需要 Θ(n)步和 Θ(n) space.
    另外一个计算方式:

    >>> def exp_iter(b, n):
            result = 1
            for _ in range(n):
                result = result * b
            return result

    使用连续乘方计算:

    b2=bbb4=b2b2b8=b4b4

    这时只需要计算3步,但是这种方法使用于幂是2的情况。改进这个连续乘方的方法使其适用于任何幂。
    bn={(b12n)2if n is evenbbn1if n is odd

    >>> def square(x):
            return x*x
    >>> def fast_exp(b, n):
            if n == 0:
                return 1
            if n % 2 == 0:
                return square(fast_exp(b, n//2))
            else:
                return b * fast_exp(b, n-1)
    >>> fast_exp(2, 100)
    1267650600228229401496703205376

    https://goo.gl/fYPkf8

    这个快速的算法是计算的复杂度降为Θlog(n),当n取值很大的时候,它跟Θ(n)的区别就很明显。如n=1000时,上面的算法计算只需要执行14次乘法,而不是1000次。

    Growth Categories

    常见计算复杂度的分类:
    1) 常数项
    这两个的复杂度是相同的。
    Θ(n) = Θ(500n)
    2) 对数
    对数的底的取值对复杂度没有影响
    3) 嵌套

    >>> def overlap(a, b):
            count = 0
            for item in a:
                if item in b:
                    count += 1
            return count

    复杂度为Θ(mn), m 外循环的长度,n为内循环的长度。
    4)低阶项
    比如下面的例子,两个数列中一个相差为1的组合有多少?

    def overlap(a, b):
        count = 0
        for item in a:
            if item in b:
                count += 1
        return count
    
    def one_more(a):
        return overlap([x-1 for x in a], a)
    
    one_more([3, 14, 15, 9])

    https://goo.gl/kYLbne
    这里的复杂度为Θ(n+n2),其中n为列表的长度

    常见的分类:
    order_growth

  • 相关阅读:
    解决IE8不兼容通过class名获取元素的方法
    移动端页面遇到过的各种坑
    强大的正则表达式
    弹性盒子布局
    vue环境搭建
    fullpage.js使用指南
    ES5原生api(1)
    双色球中奖率分析(python)
    使用python脚本的3D引擎Panda3d
    Python lambda介绍
  • 原文地址:https://www.cnblogs.com/siucaan/p/9623155.html
Copyright © 2020-2023  润新知