首先提出一个问题,如何评估算法的好坏,有人可能会说直接放线上跑跑,或者写个测试用例做个benchmark,这些方法都正确,不过有以下几个缺点。
不同的测试环境,比如机器配置不同,所测试的结果也会不同,算法的评估结果是基于特定的环境,没有普适性
在算法的评估过程中,往往都是与数据量的大小成比例关系的,不同量级的数据,结果也是大相径庭,所以模拟数据量的大小就是必要工作
算法的评估如果都需要通过测试用例,或者线上反馈去评估的话,往往耗时耗力,我们需要在算法实现的阶段就得算法的效率有一个初步评估要求,这就无法用事后统计的这种方法来实现了
基于以上原因,复杂度分析就应运而生了,复杂度分析包含时间复杂度分析,和空间复杂度分析,我们着重介绍时间复杂度分析。
In computer science, the time complexity is the computational complexity that describes the amount of time it takes to run an algorithm. Time complexity is commonly estimated by counting the number of elementary operations performed by the algorithm, supposing that each elementary operation takes a fixed amount of time to perform.
通过以上定义我们可以知道,时间复杂度是算法表现好坏的一种评估,我们假设CPU执行每一行代码的时间是一个单元时间(unit_time), 如果此代码执行了N行,那么时间估算就是 T = N * unit_time。具体我们看看以下例子。
int sum(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
return sum;
}
第二、三、七行执行了1次unit_time, 第四、五行执行了N次,所以T= 2n + 3, 这个就是这个算法评估执行时间。现在让我们想一下,如果n足够大,比如无穷大,那么2n+3可以约等于n,也就是说在n足够大得情况下,我们可以将低阶,常量,参数都给忽略了,这就是大O时间复杂度表示法,用一个算式就是 :
T(n) = O(f(n))
它不表示具体的算法执行时间,只表示这个算法随数据量级增大时间消耗的增长趋势。这样其实就可以让我们直观的判断一个算法的好坏,这就是O的来源。
让我们在回到上面的例子,如果用大O表示会是怎么样。
T(n) = O(2n + 2) 去掉常量和参数,那么T(n) = O(n)
所以上面算法的时间复杂度就是O(n)
常见的大O时间复杂度有哪些呢,他们的趋势是怎么样的呢。我给大家提供一张表,这个表需要牢记心中。
从上面表可以看出时间复杂度为O(n!) O(2^n) 的算法都是非常糟糕的,执行时间会随着量级的增大而急剧增大,基本趋于无限执行时间,这类的统称为非多项式量级。其他的我们统称为多项式量级。下面就多项式量级相关的展开讨论。
1、O(1)
算法只执行1行代码。比如
int sum(int n) {
int sum = n;
}
2、O(n)
只需要关注执行次数最多的那一段代码即可,这种也叫线性时间,比如简单查找就是属于这种,代码案例请参考上面。
3、O(logn)
这种我们称之为对数时间。比如二分查找属于这种。具体可以说明可参考 https://blog.csdn.net/li396864285/article/details/79820808
4、O(n^2)
选择排序的时间复杂度就是属于这种。当然最常见的就是两个for循环嵌套。
5、O(n!)
这种也是存在的,当然是非常慢的一种算法。比如旅行商问题
最好时间复杂度(best case time complexity):在最理想情况下,算法执行的时间复杂度
最坏时间复杂度(worst case time complexity): 在最坏情况下,算法执行的时间复杂度
接下来以一个例子来阐述这些概念。
比如有一个有序的数组,里面有n个元素依次排列,寻找一个值为i的元素。采用简单查找。
最理想情况就是,数据组第一个元素 就是值为i的元素,那么时间复杂度就是O(1), 这就是最好时间复杂度
最差情况就是,数据组第N个元素,值为i,那么时间复杂度就是O(n),这就是最坏时间复杂度
那么平均复杂度呢。我们这样假设,如果值i在第一个元素时 需要查找1次,在第二个元素时,需要查找2次,在第三个元素时,需要查找3次,在第n个元素时,需要查找n次,如果i不在数组里面,那么也是查找了n次,所以总的查找次数就是1 + 2 + 3 + …… + n +n = n(n+3)/2, 在除以n+1,算个平均值 f(n) = n(n+3)/2(n+1), 去掉低阶和常量,那么其平均时间复杂度就是为O(n)
相对于时间复杂度,空间复杂度就简单的多,只要看算法中最大的申请内存是多少。其复杂度就是多少。比如
void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i
第三行中,申请了一个大小为n的数据组,所以其空间复杂度就是为O(n)
1、算法的时间复杂度其实不是以时间角度来度量的,而是以时间增速趋势来度量
2、算法的时间复杂度按量级分可以为多项式量级和非多项式量级
3、算法时间复杂度用大O表示法表示