时间复杂度和空间复杂度

衡量一个算法的复杂度主要从时间维度和空间维度两个方面,时间复杂度是指执行算法所消耗的总时间,空间复杂度是指执行算法所占用的内存空间。

1.时间复杂度

代码执行的时间只有在跑完后才能知道的,所以在设计算法之前,只能预先估计算法的时间复杂度。

预估时间复杂度使用代码执行的总次数。假设cpu每个单元执行消耗的时间一样,也就是假设每行代码执行时间一样,用f(n)表示代码执行的总次数,比如下面代码中,f(n) = n+n。

for i in range(n): # n次
    a += 1 # n次

我们一般考虑最坏的情况,即n趋于无穷的时候,用O(f(n))描述时间复杂度,O可以理解为上界的意思,此时O(f(n)) = O(n),因为n趋于无穷的时候2n和n已经没有区别了,这就是经常遇见的复杂度。

时间复杂度 O() 描述的是一个量级,是一个预估量,常见的有:O(1)、 O(logn)、 O(n)、 O(nlogm)、 O( n 2 n^2 n2)、 O( n 3 n^3 n3)

它们的优劣顺序为(越小越好):

O(1) < O(logn) < O(n) < O(nlogm) < O( n 2 n^2 n2) < O( n 3 n^3 n3)

1)O(1)

一般不涉及循环的,时间复杂度都是常数阶,因为每行代码都只执行一次。

a = list()
b = [1,2,3]
a.append(b)

或者:

a = 1
b = 1
if a == b:
    print('a == b')
else:
    print('a != b')

等都是O(1)

2)O(n)

这就是上面举的例子,一般涉及一个循环,下面是一个复杂点的例子:

a = 1  #1次
b = 2  #1次
for i in range(n): #n次
    a += 1   #n次
    c = a+b  #n次
    print c  #n次

f(n) = 1+1+n+n+n+n = 4n+2,同样的道理,在最坏的情况下,n趋于无穷的时候,(4n+2)和n是一样的,时间复杂度为O(n),也就是说,我们在计算时间复杂度的时候,是忽略常数项和常数系数的,因为常数和无穷大的 n 不是一个量级,所以,在计算时间复杂度时,直接看循环、迭代、递归的部分

下面的while循环也是一样:

p = 0
while p < n:
    p += 1
    

3)O(log n)

x = 1
while x < n:
    x = x*2  

上面代码假设循环 a 次后x>=n,那么( 2 a 2^a 2a) * x = n,求得a = log n,所以时间复杂度为 O(log n) 。当然,如果x不是1,或者循环里面不是2倍关系而是3倍甚至10倍呢?比如下面代码:

a = [1,2,3,4,5,6,7,8]
x = len(a)
while x < n:
    x = 3*x

这样求得循环次数 a = ( log ⁡ 3 n \log_3n log3n)/4 ,在这里还是忽略常数系数1/4,底数3也忽略,都记作 O(log n),因为对量纲的影响不大, log ⁡ ? n \log_? n log?n 始终是小于 n 的,不管底数是什么。实际上底数越来越大值反而越来越小,对应的复杂度也就越小,所以我们都默认为最坏的情况,系数为1、底数为2,底数为2时可以不写,记为O(log n)。

4)O(nlog m)

这个很容易理解,见代码:

for i in range(n): #n次
    while x<m: #logm 次
        x = 3*x

因为for循环,每次循环里面又套了logm次while循环,所以为O(nlogm)

5)O( n 2 n^2 n2)

#input: a:list()
b = 0
for i in range(n):  #n次
    for j in range(n):  #n次
        if a[i] == a[j]:  #a是输入列表
            b += 1      

与上面同理,只不过第二个循环的次数与第一个一样,也可以不一样:

a = 0
for i in range(n): # n
    for j in range(m): # m
        a += 1

不过当n和m都趋于无穷大时,就可以认为n == m,复杂度为O( n 2 n^2 n2).

for、while循环 理论上是可以一直套的,套三个就是O( n 3 n^3 n3),套四个就是O( n 4 n^4 n4),不过一般时间复杂度超过O( n 3 n^3 n3),算法执行所消耗的时间就有点多了,所以一般控制在O( n 3 n^3 n3)之内,能不嵌套就不嵌套。

在实际工程当中,数据集比较大的情况下,比如n可能是数据的长度,可以使用GPU进行加速计算,有兴趣的话后面会写一篇关于python使用GPU加速计算的文章。

2.空间复杂度

一般python中的空间复杂度主要取决于变量的长度,表示临时占用的内存空间,比如:

a = 1 # O(1)
a = [1,2,3,....,n] #O(n)
a = [[1,3,...,n],[2,4,...,n]] #O(n^2)
a = [[1,2,...,n],[1,2,...,n],...,[1,2,...,n]] #O(n^n)

另外,有些算法本身空间复杂度会比较大,比如递归算法:

#a is input:list();len(a) == n
def sum(a):
    if len(a) <= 1:
        return a[0]
    else:
        return a[0]+sum(a[1:])

如果 a 的长度为 n ,那么在计算过程中会申请n个临时的内存空间,空间复杂度为O(n),另外递归算法的时间复杂度也是O(n),因为它执行了n次。

在实际写算法时,只要代码不是过于冗长,一般对空间复杂度不会太过于看重,主要是时间复杂度。

另外,以上描述的复杂度都是对n趋于无穷时算法复杂度的估计,准确说是对一种趋势的估计**,一种对最坏的趋势的估计。**在实际写代码的时候,比如如果是在刷Leetcode,输入是非常大的测试集且未知,就要以最坏的情况考虑代码;如果输入是确定的,即n的值是可观的,就可以具体估计复杂度。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

福永~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值