数据结构-01 复杂度分析

复杂度分析

时间复杂度

大 O 复杂度表示法

示例代码:计算1,2,3,…n 的累加。

def fun(n):
	sum = 0
	for i in range(1, n):
		sum += i
	return sum

从CPU的角度看这段代码的执行过程,第二行只执行1次,第 2、3 行执行 n 次,假设每行代码的执行时间都是 unit_time,这段代码的总执行时间就是 T ( n ) = ( 2 n + 1 ) ∗ u n i t t i m e T(n)=(2n + 1)*unittime T(n)=(2n+1)unittime。显然,所有代码的总执行时间 T ( n ) T(n) T(n) 与每行代码的执行次数成正比

下面的式子就是大 O 时间复杂度表示法,这种时间复杂度表示法并不具体地表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以叫大 O 复杂度表示法
T ( n ) = O ( f ( n ) ) T(n) = O(f(n)) T(n)=O(f(n)) - T ( n ) T(n) T(n) : 代码执行的时间
- n n n :数据规模的大小
- f ( n ) f(n) f(n) :每行代码执行次数总和的公式
- 公式中的 O O O 表示代码的执行时间 T ( n ) T(n) T(n) f ( n ) f(n) f(n) 成正比

常见的时间复杂度

  1. 常量阶: O ( 1 ) O(1) O(1)
  2. 对数阶: O ( log ⁡ n ) O(\log n) O(logn)
  3. 线性阶: O ( n ) O(n) O(n)
  4. 线性对数阶: O ( n log ⁡ n ) O(n\log n) O(nlogn)
  5. 平方阶: O ( n 2 ) O(n^2) O(n2)
  6. 立方阶: O ( n 3 ) O(n^3) O(n3)
  7. 指数阶: O ( n 2 ) O(n^2) O(n2)
  8. 阶乘阶: O ( n ! ) O(n!) O(n!)

O ( 1 ) < O ( log ⁡ n ) < O ( n ) < O ( n log ⁡ n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1) < O(\log n) < O(n) < O(n\log n) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
在这里插入图片描述

空间复杂度

def fun(n):
    f_list = []
    for i in range(n):
        f_list.append(i)

    for i in range(n):
        print(f_list[-i - 1])

跟时间复杂度分析一样,我们可以看到,第 3 行申请了一个大小为 n 的列表,除此之外,剩下的代码都没有占用更多的空间,所以整段代码的空间复杂度就是 O ( n ) O(n) O(n)

我们常见的空间复杂度就是 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2),像 O ( log ⁡ n ) O(\log n) O(logn) O ( n log ⁡ n ) O(n\log n) O(nlogn) 这样的对数阶复杂度平时都用不到。而且,空间复杂度分析比时间复杂度分析要简单很多。所以,对于空间复杂度,掌握刚这些内容已经足够了。

浅析最好、最坏、平均、均摊时间复杂度

在一个无序的数组(array)中,查找变量 x 出现的位置。如果没有找到,就返回 -1。

def find_x_from_list(f_list, x):
	pos = 0
	for i in range(len(f_list)):
		if x == f_list(i):
			pso = i
			return i
	if pos == 0:
		return -1

要查找的变量 x 可能出现在数组的任意位置。

  • 最好时间复杂度
    如果数组中第一个元素正好是要查找的变量 x,那就不需要继续遍历剩下的 n-1 个数据了,那时间复杂度就是 O(1)。
  • 最坏时间复杂度
    如果数组中不存在变量 x,那我们就需要把整个数组都遍历一遍,时间复杂度就成了 O(n)。

所以,不同的情况下,这段代码的时间复杂度是不一样的。为了表示代码在不同情况下的不同时间复杂度,我们需要引入三个概念:最好时间复杂度最坏时间复杂度平均时间复杂度

平均时间复杂度

最好时间复杂度和最坏时间复杂度对应的都是极端情况下的代码复杂度,发生的概率其实并不大。为了更好地表示平均情况下的复杂度,我们需要引入另一个概念:平均情况时间复杂度。

要查找的变量 x 在数组中的位置,有 n+1 种情况:在数组的 0~n-1 位置中和不在数组中。我们把每种情况下,查找需要遍历的元素个数累加起来,然后再除以 n+1,就可以得到需要遍历的元素个数的平均值,即:
1 + 2 + 3 + ⋅ ⋅ ⋅ + n + n n + 1 = n ( n + 3 ) 2 ( n + 1 ) \frac {1+2+3+···+n+n}{n+1} = \frac {n(n+3)}{2(n+1)} n+11+2+3++n+n=2(n+1)n(n+3)

所以,得到的平均时间复杂度是: O ( n ) O(n) O(n)

这个推导的过程对吗?其实是有问题的,需要将每种情况发生的概率也考虑进去,假设在数组中与不在数组中的概率都为 1 2 \frac 12 21。另外,要查找的数据出现在 0~n-1 这 n 个位置的概率也是一样的,为 1 n \frac 1n n1。所以,根据概率乘法法则,要查找的数据出现在 0~n-1 中任意位置的概率就是 1 2 n \frac 1{2n} 2n1。下面的公式展示正确的平均时间复杂度的计算过程: 1 × 1 2 n   + 2 × 1 2 n + 3 × 1 2 n + ⋅ ⋅ ⋅ + n × 1 2 n + n × 1 2 = 3 n + 1 4 1\times \frac 1{2n} \ + 2\times \frac 1{2n} + 3\times \frac 1{2n} + ···+n\times \frac 1{2n}+n\times \frac 1{2} = \frac{3n+1}{4} 1×2n1 +2×2n1+3×2n1++n×2n1+n×21=43n+1这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度。

由此计算这段代码的加权平均时间复杂度仍然是 O ( n ) O(n) O(n)

大部分情况下,我们并不需要区分最好、最坏、平均三种复杂度。平均复杂度只在某些特殊情况下才会用到,而均摊时间复杂度应用的场景比它更加特殊、更加有限。

均摊时间复杂度

def fun():
	f_list = []
	count = 0

	
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值