前言
为了让广大读者轻松学习,完全理解内容表达含义。我会用最通俗易通的方式结合图文表达,让每位读者完全熟记每种数据结构和算法的优缺点!今天我们分享的是时间复杂度和空间复杂度,因为只要讲到数据结构与算法,就一定离不开时间、空间复杂度分析。
时间复杂度
首先,什么是时间复杂度?简单的讲就是指执行这个算法所需要的执行时间,分为事先统计法和事后统计法,事后统计法就是通过一些监控,指标,来评估算法的时间复杂度,但是这样算法的执行时间,会受到很多因素的影响,例如硬件、运行时环境、数据规模等。所以我们采用事前统计法来评估一个算法不依赖于其他环境因素的影响下的效率。
大 O 复杂度表示法
大O复杂度表示法是指所有代码的执行时间 T(n) 与每行代码的执行次数 n 成某种有规律的函数。例如以下例子一:
int count(int n) {
int sum = 0;
for (int j = 0; j <= n; ++j) {
sum = sum + j;
}
}
上图代码中,假如每行代码的执行一次的时间为一个单位的time,那么第三行1个time,第4,5行都运行了n次,那么总执行时间就是T(n)=(2n+1)time,通过公式我么你可以看出,所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比,即T(n) = O(f(n)),T(n) 表示代码执行的时间;n 表示数据规模的大小;f(n) 表示每行代码执行的次数总和,因为代码执行次数总和是一个公式,所以用 f(n) 来表示。公式中的 O,表示代码的执行时间 T(n) 与 f(n) 表达式成正比,在例如下面这个例子二:
int count(int n) {
int sum = 0;
for (int j = 0; j <= n; ++j) {
for (int i = 0; i<=n; ++i){
sum = sum + j + i;
}
}
}
以上代码中,代码执行总次数是第二行1个time,第三行执行了n次,所以是n个time第4,5行执行了n^2次,所以总执行时间是(1+n+n^2)time,所以用大O表示就是时间复杂度是T(n)=O(f(n))=O(1+n+n^2)。这就是大 O 时间复杂度表示法,大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度,简称时间复杂度。
时间复杂度分析方法
我们在计算一个算法的时间复杂度的时候通常会忽略掉公式中的常量、低阶、系数,只统计一个最大量级的记录就行了,所以通常统计的方法有以下三种:
-
只关注循环执行次数最多的一段代码
例如在例子一中:第2行只执行了一次,是常量级的执行时间,所以与n无关,但是第3、4行执行了n次,所以我们只关系执行次数最多的第3、4行,所以时间复杂度是O(n)。
int count(int n) {
int sum = 0;
for (int j = 0; j <= n; ++j) {
sum = sum + j;
}
}
-
总复杂度等于量级最大的那段代码的复杂度
如以下例子,在同一个getCount(int n)方法中,第一段for循环的时间复杂度是O(n),第二段for循环的时间复杂度是O(n^2),所以总的复杂度等于量级最大的那段代码的复杂度,所以方法getCount的时间复杂度是O(n^2)。
public void getCount(int n) {
int sum = 0;
for (int i = 0; i <= n; i++) {
sum = sum + i;
}
int val = 0;
for (int j = 0; j <= n; j++) {
for (int z = 0; z <= n; z++) {
val = val + j + z;
}
}
}
-
嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
同样例如下面的例子,第3行的执行次数是n次,第4、5行的执行次数是n*n=n^2次,所以count方法的时间复杂度是O(n^2)。
int count(int n) {
int sum = 0;
for (int j = 0; j <= n; ++j) {
for (int i = 0; i <=n; i++){
sum = sum + j + i;
}
}
常见时间复杂
O(1)常数阶 | O(2^n)指数阶 | O(logn)对数阶 |
O(n!)阶乘阶 | O(n)线性阶 | O(nlogn)线性对数阶 |
O(n^2)平方阶 | O(n^3)立方阶 | O(n^k)k方阶 |
空间复杂度
什么是空间复杂度?类比时间复杂度,复杂度是表示算法的存储空间与数据规模之间的增长关系,举个例子:
void setVal(int n) {
int i=0;
int[] a = new int[n];
for(;i<a.length;i++){
a[i]=i;
}
}
如上图,我们声明一个存储变量i,但是由于i是常量阶与n无关,所以我们可以勿略不计,但是第4行我们申请了一个大小是n的int数组,其余没有别的申请空间,所以这个方法的空间复杂度就是O(n),常见的空间复杂度有O(1)、O(n)、O(n^2),至于O(logn)、O(nlogn),几乎很少碰到,所以本文不做讨论,以上就是空间复杂度的分析,更多请类比时间复杂度,因为时间复杂度在一般的面试中被问及的更多,所以讲的比较详细。
最好、最坏、平均、均摊时间复杂度
最后在分享一下时间复杂度中的几个概念,因为这些概念在面试中被问到的频率并不会太多,所以只做一些简单的说明。
-
最好情况时间复杂度
定义:最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度
-
最坏情况时间复杂度
定义:最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度
我们还是通过例子来说明:
int count(int arr[],int x) {
int val = -1;
for (int j = 0; j <= arr.length; j++) {
if(arr[j] == x){
val = arr[j];
}
}
return val;
}
上图例子中,我们从数组arr中查询x的位置,那么最好的情况下就是arr[0]就是x的值,那么只需要查找一次就可以得到x的索引位置并返回,那么我们就说上述代码的最好情况时间复杂度是O(1)。如果在arr中并没有值等于x,那么就需要遍历整个数组,然后返回-1,那么最坏时间复杂度就是O(n)。
-
平均情况时间复杂度
这个概念有点复杂,需要一点点的数学基础,我们还用上图的例子来说明,我们查找数组中值是变量x的位置,那么一共有n+1种可能:在数组[0~n-1]位置上和 不在数组中,我们把每种情况下,查找x需要遍历的次数累加起来,然后除以n+1,就可以得到需要遍历的元素个数的平均值,就是:然后,我们在计算时间复杂度的大 O 标记法中,可以省略掉系数、低阶、常量,所以,咱们把刚刚这个公式简化之后,得到的平均时间复杂度就是 O(n)。虽然结论是对的,但是计算过程不太准确,因为变量x在不在数组中的概率不一样,我们假在不在数组中的概率都是1/2,那么在数组[0~n-1]每个位置上的概率都是一样的,是1/n,那么根据乘法法则,x在数组中出现的概率就是1/(2n),那么前面推导的公式就变成:
这个值就是概率论中的加权平均值,也叫作期望值,所以平均时间复杂度的全称应该叫加权平均时间复杂度或者期望时间复杂度。
-
均摊情况时间复杂度
均摊情况时间复杂度这里我们不做分析,因为这个复杂度只能说是考试中才有可能考的,面试中基本不会过问,所以我们不做分析,如果又想深入研究的建议google,或者百度大法
结尾
好了,感谢阅读,本篇文章我们主要讲了时间复杂度和空间复杂度的含义以及分析方法,和一些时间复杂度情况,更多的篇幅还是主要讲解的时间复杂度的分析,这也是因为在面试中,几乎一旦被问到就一定会问时间复杂度的,空间复杂度相对比较容易识别,最后再给大家分享一下常见的数据结构和算法的复杂度总结,那么,我们下期见!
数据结构复杂度对比:
常用排序算法复杂度对比: