文章目录
- 复杂度
- 时间复杂度
- 规则
- 常见的时间复杂度
- 最好、最坏、平均时间复杂度
- 空间复杂度
- 规则
- 常见的空间复杂度
复杂度
复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率与数据规模之间的增长关系。
复杂度分析可以在初期帮助程序员预估该程序的性能耗费。
时间复杂度用于表示算法的时间耗费与数据规模增长之间的关系
空间复杂度用于表示算法的存储空间与数据规模增长之间的关系
时间复杂度
规则
- 总复杂度等于量级最大的那段代码的复杂度,比如说一个程序中存在两段不同时间复杂度的代码块:
int f(int n) {
int sum = 0;
for (int i = 0; i < n; ++i) {
sum ++;
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
sum ++;
}
}
return sum;
}
第一个for循环的时间复杂度T1,和第二个嵌套for循环的时间复杂度T2分别为:
那么整个程序的时间复杂度为:
- 如果量级大的代码块有多个,而我们无法事先评估谁的量级大,就不能简单地省略其中一个,例如:
int f(int n, int m) {
int sum = 0;
for (int i = 0; i < n; ++i) {
sum ++;
}
for (int i = 0; i < m; ++i) {
sum ++;
}
return sum;
}
该函数的时间复杂度为:
- 乘法法则: 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积,例如:
int f(int n, int m) {
int sum = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
sum += j;
}
}
return sum;
}
该函数的时间复杂度为:
常见的时间复杂度
对于常量复杂度而言,执行时间不随 n 的增大而增长的代码,我们都记作 O(1) 。 一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)
对于对数阶复杂度而言, 不管是以 2 为底、以 3 为底,还是以 10 为底,我们可以把所有对数阶的时间复杂度都记为O(logn)
对于指数阶、阶乘阶复杂度而言, 当数据规模 n 增大,算法的执行时间会急剧增加 ,因此这两类时间复杂度的算法是非常低效的算法不推荐使用
最好、最坏、平均时间复杂度
上述情况是程序必须执行完所有代码得出的时间复杂度。而在很多情况下,我们不需要进行完整的遍历或递归,例如查找一个数组中是否存在5:
int val[n];
bool find() {
for (int i = 0; i < n; i++) {
if (val[i] == 5) {
return true;
}
}
return false;
}
最好情况时间复杂度是最理想情况下执行这段代码的时间复杂度,体现在上述代码中,就是数组第一个元素是5,因此最好情况时间复杂度为O(1)
最坏情况时间复杂度是最糟糕情况下执行这段代码的时间复杂度 ,体现在上述代码中,就是数组中不存在元素5,因此最坏情况时间复杂度为O(n)
平均情况时间复杂度是考虑所有可能发生的情况、概率、以及对应耗费的时间,然后取平均值,体现在上述代码中,就是考虑n+1种情况(5出现在0~n-1位置 和 5没有出现)及其概率、对应耗费的时间,得到:
由于时间复杂度可以省略掉系数、低阶、常量,把这个公式简化之后得到的平均时间复杂度就是 O(n)
空间复杂度
规则
规则与时间复杂度的三点类似,不再赘述
我们可以通过空间复杂度,来预估程序所耗费的内存。比如:
const int N = 1000000;
int[] val = new int[N];
int型占四字节,对于O(n)空间复杂度的程序来说,当n为1e6时,耗费的内存为:
常见的空间复杂度
比较常见的空间复杂度有:
相比时间复杂度, O(logn)、O(nlogn)
等对数阶复杂度平时几乎用不到~