如何理解算法的时间和空间复杂度

发布时间:2022-01-20编辑:RainNight阅读(494)

    如何理解算法的时间和空间复杂度


    算法(Algoritthm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。

    具体如何衡量不同算法之间的优劣呢?

    主要还是从算法所占的【时间】和【空间】两个维度去考量。

    • 时间维度:是指执行当前算法所消耗的时间,我们通常用【时间复杂度】来描述。
    • 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用【空间复杂度】来描述。

    因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是【鱼和熊掌】,不可兼得的,那么我们就需要从中去取一个平衡点。

    【时间复杂度】和【空间复杂度】的计算方式。

    一、时间复杂度


    我们需要知道一个算法的【时间复杂度】,很多人首先想到的方法就是把这个算法程序运行一遍,那么它所消耗的时间就自然而然知道了。

    这个方式可以吗?当然可以,不过它也有很多弊端。 这种方式非常容易受运行环境的影响,在性能高的机器上跑出的结果与在性能低的机器上跑出的结果相差会很大。而且对测试时使用的数据规模也有很大关系。再者,并我们在写算法的时候,还没有办法完整的去运行呢。

    因此,另一种更为通用的方法就出来了:【大O符号表示法】,即T(n)=O(f(n))

    def extendSum():
        sum = 0
        start = 1
        end = 101
    
        for i in list(range(start, end, 1)):
            sum += i
    
        return sum
    
    

    通过【大O符号表示法】,这段代码的时间复杂度为:O(n),why?

    在大O符号表示法中,时间复杂度的公式是:T(n) = O(f(n)),其中f(n) 表示每行代码执行次数之和,而O表示正比例关系,这个公式的全称是:算法的渐进时间复杂度。

    假设每行代码的执行时间都是一样的,我们用1颗粒时间来表示,那么这个例子的第一行耗时是1个颗粒,第三行的执行时间是n个颗粒时间,第四行的执行时间也是n个颗粒时间 (第二行和第五行是符号,暂时忽略),那么总时间就是1颗粒时间+n颗粒时间+n颗粒时间,即(1+2n)个颗粒时间,即:T(n) = (1+2n)*颗粒时间,从这个结果可以看出,这个算法的耗时是随着n的变化而变化,因此,我们可以简化的将这个算法的时间复杂度表示为:T(n)=O(n)

    为什么可以这么去简化呢,因为大O符号表示法并不是用于来真实代表算法的执行时间的,它是用来表示代码执行时间的增长变化趋势的。

    所以上面的例子中,如果n无限大的时候,T(n)=time(1+2n)的常量1就没有意义了,倍数2也意义不大。因此直接简化为T(n)=O(n)就可以了。

    常见的时间复杂度量级有:

    • 常数阶O(1)
    • 对数阶O(logN)
    • 线性阶O(n)
    • 线性对数阶O(nlogN)
    • 平方阶O(n2)
    • 立方阶O(n3)
    • k次方阶O(n^k)
    • 指数阶(2^n)

    从上至下依次的时间复杂度越来越大,执行的效率越来越低。

    1.常数阶O(1)


    无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是O(1)

    def equivarianceSum():
        n = 100
        sum = (1 + n) * n // 2
        return sum
    

    上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时间复杂度。

    2.线性阶O(n)


    def extendSum():
        sum = 0
        start = 1
        end = 101
    
        for i in list(range(start, end, 1)):
            sum += i
    
        return sum
    

    这段代码,for循环里面的代码会执行n遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度。

    3.对数阶O(logN)


    # 乘积
    def multiply():
    
        n = 4
        i = 1
    
        while i < n:
            i *= 2
    
        return i
    
    

    在while循环里面,每次都将i乘以2,乘完之后,i距离n就越来越近了。我们试着求解一下,假设循环x次之后,i就大于2了,此时这个循环就退出了,也就是说2的x次方等于n,那么x = log2^n 也就是说当循环log2^n次以后,就结束,因此这个代码的时间复杂度为:O(log)

    4.线性对数阶O(nlogN)


    线性对数阶O(logN)其实非常容易理解,将时间复杂度为O(logn)的代码循环n遍的话,那么它的时间复杂度就是n*O(logN),也就是O(nlogN)。

    def multiply():
    
        n = 4
        i = 1
        for j in n:
            while i < n:
                i *= 2
                
        return i
    

    5. 平方阶O(n2)


    平凡阶O(n2)就更容易理解了,如果把O(n)的代码再嵌套循环一遍,它的时间复杂度就是O(n2)了。

    def traverse():
        sum = 0
    
        n = [1, 2, 3, 4, 5]
    
        for i in range(n):
            for j in range(n):
                sum += i
    
        return sum
    

    这段代码其实就是循环了2层n值循环,他的时间复杂度就是O(n*n),即O(n2)。

    如果将其中一层循环的n改成m,即:

    def traverse():
        sum = 0
    
        n = [1, 2, 3, 4, 5]
        m = [1, 2, 3, 4]
    
        for i in range(n):
            for j in range(m):
                sum += i
    
        return sum
    

    那它的时间复杂度就变成了 O(m*n).

    二、空间复杂度


    既然时间复杂度不是用来计算程序具体耗时的,那么我也应该明白,空间复杂也不是用来计算程序实际占用的空间的。

    空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用S(n)来定义。

    空间复杂度比较常用的有:O(1)、O(n)、O(n2),我们下面来看看:

    1.空间复杂度O(1)


    如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为O(1)

    def equivarianceSum():
        n = 100
        sum = (1 + n) * n // 2
        return sum
    

    它的空间复杂度s(n)=O(1)

    2.空间复杂度O(n)


    def multiply():
    
        n = 4
        i = 1
        for j in n:
            while i < n:
                i *= 2
                
        return i
    

    有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 S(n) = O(n)。

关键字数据结构与算法

Collect from 雨夜的博客 雨夜的博客