如果时间无限, 我们可能不会考虑效率; 如果空间无限, 我们可能也不会考虑节省空间.
但很可惜, 时间、空间都是有限,所以,我们追求效率,节省空间;对于一段代码,一个程序也是如此。
1. 必要性
事后统计法:写完代码,在机器上跑一遍,计算其效率
缺点:
- 事后统计受机器性能影响
- 事后统计受数据量的影响
鉴于事前统计(大O法),简单便捷不受机器数据量的影响,对我们在实现代码的过程中具有明显的意义。
2.大O法
时间复杂度:
我们普遍达成共识,一行代码的运行时间为一个 unit_time, 其为常量,那么效率就是运行了多少行代码?
int
我们给定一个n,如上代码,第2,3行运行了一次,所以是 2 * unit_time,第4,5行运行了n遍,所以是 2 * n * unit_time, 那么这段代码的运行时间就是 (2n + 2 )* unit_time
简单表示:
加法原则:
虽然我们不知道n的大小,但我们可以知道随者n的增大,2n + 2,增长快慢与n是一致的,即两者是同一个数量级的,所以在加法原则中,我们只需要知道并表示变化最快的那一项就可以了,即 O(n)
乘法原则:循环嵌套
int cal(int n) {
int ret = 0;
int i = 1;
for (; i < n; ++i) {
ret = ret + f(i);
}
}
int f(int n) {
int sum = 0;
int i = 1;
for (; i < n; ++i) {
sum = sum + i;
}
return sum;
}
我们看第五行,f(i) 并不是一个普通的函数,而是一个有循环的函数,其实我们只有有高中的数学知识,就应该可以知道第五行是运行了 n * n 次的(外层运行了n次,里层运行了n次,所以就是运行了n * n 次),所以该函数的大O表示就是 O(n^2) ,乘法法则一般都是出现在循环嵌套中的。
常见的时间复杂度:
O(1), O(logn), O(n), O(nlogn), O(n^2), 时间复杂度依次递增
O(1), 常量级
譬如 2 * unit_time, 3 * unit_time.... 他们的运行时间不受 n 的影响, 为一个常量
int i = 8;
int j = 9;
int sum = i + j;
不管代码有多少行, 只有没有循环,递归,不受n的影响,那么我们就认为就是常量级的, 记作O(1)
O(logn), 对数级
如果数据量n呈现等比数列进行递减, 那么一般都是O(logn)
i=1;
while (i <= n) {
i = i * 2;
}
假设n是100, i第一次循环是2, 第二次是2*2, 第三次是2*2*2, 数据呈等比的趋势接近100, 当2^n次方等于100的时候, 退出循环,那么n=log100(以2为低,100的对数),我们不管这是以2的倍数,还是3的倍数或者其他也好,他们的数量级都是一样的,所以我们统一表示为 O(logn)
O(n), 线性阶
可以参考,一个普通的循环
O(nlogn), 线性对数阶
参考乘法法则, 就是对数阶外面嵌套了一层线性阶 O(n) * O(logn)
O(n^2), 平方阶
参考乘法原则
空间复杂度:
void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i <n; ++i) {
a[i] = i * i;
}
for (i = n-1; i >= 0; --i) {
print out a[i]
}
}
对于整段代码, 我们只是额外在第三行申请了n的空间用于存储, 其他的都是常量级的, 不受n的影响.所以我们就认为空间复杂度为 O(n), 比较常见的是 O(1), O(n), O(n^2),掌握这些就够了
3.最好,最差,平均时间复杂度
# 在arry数列中找出t的索引, 如果没有则返回-1
最好时间复杂度:
如果我们找的是1, 那么我们只需要找一次, 因为第一个数就是它了, 就是O(1)的复杂度
最差时间复杂度:
如果我们找的是4, 刚好在最后,或者数列中没有这个数,我们就需要遍历n次了, 则复杂度为 O(n)
那我们究竟要用哪一种表示呢?
好像哪一种都不能很好地解释效率, 因为他们都没有考虑各种情况, 最好最快都是极端的情况, 所以我们引入平均时间复杂度这个概念, 需要一点点概率论的知识.
我们需要找一个数 t, 他在每个位置的概率假设都一样, 为 1/n(如果我们考虑可能不在数组内, 我们可以在数组最后加一个空间,如果遍历到它, 则代表不存在, 那么平均每个位置的概率为1/n+1, 我们这里只用1/n就行了)
那么根据加权平均(即期望值): 1 * 1/n + 2 * 1/n + 3 * 1/n +.... + n * 1/n = (n + 1) / 2, 即 O(n)
我们发现这和最差的时间复杂度一样的, 那我们有没有简单点的办法求解呢?
假设随着n的增大, 可以感受到我们要查找的数刚好在第一位的 概率是及其小的, 可能大部分情况下,在25%, 50%, 75%的位置里, 既然n很大, 那么是不是可以理解成 大部分时间内都需要遍历几分之几n呢, 就和 O(n)的复杂度一样了, 在这里我们只关注大部分情况下的时间复杂度.
只要你多练,相信是可以很快就得出答案