复杂度分析
复杂度分析是整个算法学习的基础和重点,当开发人员在编写代码时,可以粗略地估算代码的执行效率是非常重要的;
所有代码的执行时间 T(n) 与每行代码的执行次数 f(n) 成正比,用公式可以表示为:
T(n) = O(f(n))
所以我们一般使用 大 O 时间复杂度表示法来表示时间复杂度;
时间复杂度
这里将使用网上找的几个例子来学习时间复杂度的计算:
例子一:
1 | int func1(void) { |
例子二:
1 | int func2(int n){ |
上述两个例子中,分别计算的次数为:
- 2
- 3n+3
常见的时间复杂度分析
O(1)
在上述例一中的时间复杂度就是 O(1)—— 表示常量级时间复杂度;
一般情况下,只要算法中不存在循环语句,递归语句,即使有成千上万行代码,其时间复杂度也是 O(1);
O(logn) && O(nlogn)
对数时间复杂度非常常见,同时也是比较难分析的一种时间复杂度
我们来用一个例子来详细还原一个这种时间复杂度的计算:
1
2
3
4
5void func(int n) {
for (int i = 1; i < n; i *= 2) {
printf("xi xi xixixi")
}
}首先,当 n = 8:
- i = 1 时 -> 判断 1 < 8 成立;i = 1 * 2 = 2,打印文本
- i = 2 时 -> 2 < 8 成立;i = 2*2 = 4,打印文本
- i = 4 时 -> 4 < 8 成立;i = 4 * 2 = 8,打印文本
- i = 8 时 -> 8 < 8 不成立;程序结束
于是,其执行时间 T(8) = 3(for 循环)
+ 3(printf)+ (3+1)(i<n) + 1(int i = 1)-> 3 * (3) +2因为对这个代码的时间复杂度的影响最大的就是 i< n && i =2 ;*于是我们可以简化为:T(8) = 3, 只关注 for 循环语句;
于是,得出规律:
2^3 = 8;
2^4 = 16;
…
2^T(n) = 3
我们知道,对于对数而言,不论是以 2,3,10 为低,都是可以互相转换的,于是可以统一采用 logn 来表示
从而推出:T(n) = 3log2n +2 = O(logn)
O(n^2)
n 方的复杂度也十分常见,在代码中常表示为 for 嵌套或者 while 循环;
这里也是用一个例子来进行学习:
1 | void func2(int n) { |
这里嵌套了两层 for 循环,可以很明显的看出其时间复杂度为 O(n^2)
当然,如果是 n 层嵌套,那么其时间复杂度为:O(n^n)
时间复杂度对比
名称 | 时间复杂度 |
---|---|
常数时间 | O(1) |
对数时间 | O(logn) |
线性时间 | O(n) |
线性对数时间 | O(nlogn) |
二次时间 | O(n^2) |
三次时间 | O(n^3) |
指数时间 | O(2^n) |
空间复杂度分析
空间复杂度全称就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系
这段代码仅用于举例:
1 |
|
可以看到我们申请了一个大小为 n 的数组,于是其空间复杂度为 O(n);
常见的空间复杂度就是 O(1), O(n), O(n^2),类似对数的复杂度都用不到
最好、最坏、平均、均摊时间复杂度
最好、最坏时间复杂度
上代码:
1 |
|
按照时间复杂度分析,这段代码的时间复杂度为 O(n)
但是在实际情况中,当我们在一个数组中查找某个数据,并不一定需要将整个数组遍历一遍;
可能会出现比较极端的情况,例如 一次 就查找到了数据或者最后才查找到数据;
平均时间复杂度
平均时间复杂度应该如何分析呢?
要查找的变量 x 在数组中的位置,有 n+1 种情况;在数组的 0~n-1 的位置中和不在数组中,将每种情况下,需要遍历的元素个数累加起来,然后再除以 n+1,就可以得到需要遍历的元素个数的平均值:
1+2+3+…+n+n/n+1 = 1/2 n = O(n)
均摊时间复杂度
均摊时间复杂度,以及它对应的分析方法,摊还分析(或者叫做平摊分析)
借助一段代码来学习:
1 |
|
我先来解释一下这段代码。这段代码实现了一个往数组中插入数据的功能。当数组满了之后,也就是代码中的 count == array.length 时,我们用 for 循环遍历数组求和,并清空数组,将求和之后的 sum 值放到数组的第一个位置,然后再将新的数据插入。但如果数组一开始就有空闲空间,则直接将数据插入数组。
- 最理想的情况下,是该数组一直有空闲空间,只需要插入数据即可,时间复杂度为 O(1)
- 最坏情况下,是插入数据时,数据已满,时间复杂度为 O(n)
那么平均时间复杂度为多少呢?
答案是 O(1)
因为对于每个数据的插入而言,其数组满的情况都被分解为每次插入了,例如已经插入了 9 次数据时,第十次插入时发生了 for 循环,那么这一次的复杂度也被均摊到前 9 次了。
这也就是均摊时间复杂度的由来了