使用数据结构或者算法解决问题,如何判断这种解法的优劣?
从2个维度去考察:
- 时间复杂度:用来评估代码运行的时间趋势
- 空间复杂度:用来评估代码运行的内存占用规模
时间复杂度
下面的代码是求数字1到n的和,那如何评估计算机执行这段代码的时间?
int sum(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
假定计算机执行每行代码的时间是单位U。那for循环要执行n次,执行时间就是n倍,那这段代码的执行时间是:
T(n)=(2 + n) * U
时间复杂度不是精确计算代码的执行时间,而是评估随着数据规模的增加,执行时间的增加趋势。
所以可以使用大O表示法,上述公式可以转成T(n)=O(2 + n) 。进一步,当n足够大时,常数可以忽略,变成T(n)=O(n) 。
经过上文的分析,所有的代码都可以使用大O表示法来评估时间复杂度。常见的时间复杂度有以下几种。
O(1)
O(1)是常量阶级别的时间复杂度,它是指一行或者多行代码,即使包含常数级别的循环或者递归,其时间复杂度也是O(1)。下面的多行代码,其时间复杂度就是O(1)。
int a = 1;
int b = 2;
if (b > a) {
std::cout << "b > a" << std::endl;
}
for (int i = 0; i < 10; i++) {
std::cout << i << std::endl;
}
O(logn)
O(logn)是对数阶级别的时间复杂度,一般代码中会包含循环,但是循环次数没有那么多。
void test1(int n) {
int k = 0;
while (k < n) {
n = k * 2;
}
}
上述代码中,根据公式2^x=n,代码循环的次数是以2为底的logn,所以其时间复杂度是O(logn)。
O(n)
O(n)是线性阶级别的时间复杂度,一般代码中会包含循环或者递归,并且其数据规模随着变量线性增加。
void test2(int n) {
int k = 0;
while (k < n) {
n = k * 2;
}
}
O(n^2)
O(n^2)是平方阶级别的时间复杂度,代码中会包含双重循环,并且其数据规模随着变量增加。这个时间复杂度非常高,实际应用中是优化的重点对象。
void test3(int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
std::cout << i * j << std::endl;
}
}
}
空间复杂度
空间复杂度表示代码运行过程中内存占用和数据规模之间的关系。常见的空间复杂度包括O(1),O(n),O(n^2)。
比如,下面的代码,最多有num,sum,i三个变量的内存占用,所以空间复杂度是常量阶O(1)
int sum(int num) {
int sum = 0;
for (int i = 1; i <= num; i++) {
sum += i;
}
return sum;
}
而下面的代码,动态分配的内存空间随着n线性增加,所以空间复杂度是O(n)。
void test4(int n) {
int *p = (int *)malloc(n * sizeof(int));
}
阅读原文,关注[空与一]