![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/2d4155650b305438104f112dcd89d404.gif)
算法的时间复杂度
作用:通过分析算法时间复杂度来判断哪个程序更优秀。
这里给大家列出数学上的解释:
时间频度(T(n)):语句执行的次数。
假定辅助函数f(n),当n趋近于无穷大时,T(n)/f(n)的极限为不为零的常数时,则记作T(n)=O(f(n))。
O(f(n))则是时间复杂度。
其实这里看不懂也没有关系,并不影响我们去计算时间复杂度。
忽略(极限思想):在计算时间复杂度时,经常会忽略一些对于算法整体的时间复杂度影响不大的项。例如:常数项、低次项、系数。
所以在计算时间复杂度时,一般要进行三步:
- 加法常数可忽略。
- 只保留最高阶项。
- 去除最高阶项的系数。
常见的时间复杂度
首先明确:递归的时间复杂度计算,其实就是:递归的次数*每次递归中的操作次数。 递归和树型结构是息息相关的。如果将递归的时间复杂度以树状来表示,就是二叉树节点个数*递归中操作次数。
- 算法时间复杂度从小到大依次为(附带举例):
- 常数阶
O(1)
:
int n = 1000;
System.out.println("Hello,World");
- 对数阶
O(logn)
:对数阶可以忽略底数,证明方法略。
int i=1;
while(i<n){
i=i*2; //每次i*2距离n更近,程序只需要执行log以2为底n的对数次即可。即为logn。
}
- 线性阶
O(n)
:
for(int i = 1; i<=n;i++){
System.out.println("Hello,World");
}
- 线性对数阶
O(nlogn)
:可以用上面提到的递归解法,递归次数*每次递归中的操作次数。
pyblic static void calc(int l,int r){
if(l>=r) return ;
for(int i = l;i <= r ; i++){ /**print XXX **/ }
int mid = (l+r) / 2;
calc(l,mid);
calc(mid+1,r); //这里是分治算法,是对数阶。
}
calc(1,n);
因为使用了分治算法,这里每次递归中的操作次数都减半了,所以直接套公式不方便得出结论。
针对这种类型的题目可以这样理解:
- 平方、立方
O(n^2),O(n^3)
:双重for循环、多重for循环。 - 指数阶
O(2^n)
int fib(int n) {
if(n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
解析如下:
- 阶乘
O(n!)
:
这里举一个利用回溯算法的例子,利用回溯算法可以解决排列/组合/子集之类的问题。这里先挖一个坑,暂不介绍。
//这是一个产生排列数的程序,时间复杂度当然是n!。
public void dfs(int depth){
Boolean [] used = new Boolean[10]; //举例
ArrayList per = new ArrayList() ; //举例
if(depth == n){
//print per
return;
}
for(int i = 0;i<n;i++){
if(used[i]) continue;
used[i] = true;
per.add(i);
dfs(depth+1);
per.remove(per.size()-1);
used[i] = false;
}
}
dfs(0);
- 各种时间复杂度图解:
平均时间复杂度、最坏时间复杂度、空间复杂度
-
平均时间复杂度
平均时间复杂度是指所有可能的输入实例等概率出现的情况下,算法的运行时间。 -
最坏时间复杂度
在讨论时间复杂度时,均指最坏情况下的时间复杂度。 保证了算法的运行时间不会超过最坏时间复杂度。
比如:
for (int i = 1, j = 1, sum = 0; i <= n; i++) {
while (j <= n && sum + a[j] <= T) sum += a[j++];
sum -= a[i];
}
因为j是一直增加的,所以对于整段代码。最坏O(n)。对于每个i,内层最坏O(n),均摊O(1)。
- 空间复杂度
作用:度量算法在运行过程中临时占用存储空间的大小。
在算法分析时,主要讨论时间复杂度,一些缓存产品和算法(基数排序)本质就是用空间换时间
主要的计算:
静态数组的长度。
递归的深度(栈上消耗的空间)栈内存较少。
动态new的空间(堆上消耗的空间)
附建议读者参考的资料: 代码随想录