一、时间复杂度的概念价值:
对于算法进行特别具体的细致分析虽然很好,但在实践中的实际价值有限。对于算法的时间性质和空间性质,最重要的是其数量级和趋势,因此,一般用“完成基本运算的时间总和的量级”来描述算法的时间复杂度即可。
要获得算法的时间复杂度,最直观的想法是把算法程序运行一遍,自然可以获得。但实践中往往受限于测试环境、数据规模等因素,直接测试算法要么难以实现,要么误差较大,而且理论上也没必要对每个算法都进行一遍测试,只需要找到一种评估指标,获得算法执行所消耗时间的基本趋势即可。
二、时间复杂度趋势比较图:
三、如何计算时间复杂度:
1、分析算法时,存在几种可能的考虑:
算法完成工作最少需要多少基本操作,即最优时间复杂度
算法完成工作最多需要多少基本操作,即最坏时间复杂度
算法完成工作平均需要多少基本操作,即平均时间复杂度
2、时间复杂度的几条基本计算规则:
求解算法复杂度一般分以下几个步骤:
- 找出算法中的基本语句,算法中执行次数最多的语句就是基本语句,通常是最内层循环的循环体;
- 计算基本语句的执行次数的数量级,只需计算基本语句执行次数的数量级,这样能够简化算法分析,使注意力集中在最重要的一点上:增长率。
- 用大Ο表示算法的时间性能:将基本语句执行次数的数量级放入大Ο记号中。
其中用大O表示法通常有三种规则:
- 用常数1取代运行时间中的所有加法常数。即一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1);
- 只保留时间函数中的最高阶项;
- 如果最高阶项存在,则省去最高阶项前面的系数;
3、通常,一个算法所花费的时间与代码语句执行的次数成正比,算法执行语句越多,消耗的时间也就越多。一个程序的时间复杂度最后算下来应该是个多项式,此时判断一个算法的效率时,往往只需要关注操作数量的最高次项,其它次要项和常数项可以忽略;另外,最高次项的系数也可以忽略。因为系数只改变了函数的陡峭程度,而不改变函数的总趋势。
4、在没有特殊说明时,我们所分析的算法的时间复杂度都是指最坏时间复杂度
四、各种量级的时间复杂度实例:
常数阶O(1):
int i = 1;
int j = 2;
int k = 1 + 2;
不会随着问题规模n的变化而变化,算法时间复杂度为常数阶。
对数阶O(log n):
常见的有 二分查找算法
int i = 1, n = 100;
while (i <= n)
{
i = i * 2;
}
循环以2的倍数来逼近n,也就是说 2^x <= n 小于等于n时会执行循环体,即x次数,于是得出x<=logn。也就是说上述循环在执行logn次之后,便结束了,因此上述代码的时间复杂度为O(log n)。
线性阶O(n):
int j = 0;
for (int i = 0; i < n; i++)
{
j = i;
j++;
}
线性对数阶O(nlogN):
for (int m = 1; m < n; m++)
{
int i = 1;
while (i <= n)
{
i = i * 2;
}
}
线性循环里面套对数级循环。
平方阶O(n²):
int k = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
k++;
}
}
指数阶O(2^n):
int fib(int n)
{
if(n==0)
return 0;
else if(n==1)
return 1;
return (fib(n-1)+fib(n-2))%1000000007;
}
斐波那契数列就是一个典型的例子:
五、根据时间复杂度推测算法
一般在leetcode上,10^4、10^5次方的数据规模应该对应线性时间复杂度的算法。
六、刷题过程中降低时间复杂度的一般思路
算法(二)降低时间复杂度的方法_如何降低算法复杂度_尒龍同学的博客-CSDN博客
1、充分消化已有信息,预处理,数学总结
2、使用某种数据结构
优先级队列使用的两种场景:
想要根据Map的value值对Map进行排序
想要对某几个元素的集合进行排序,此时可以针对这几个元素定义一个类class
4、空间换时间
算法的时间复杂度和空间复杂度是可以相互转化的。
谷歌浏览器相比于其他的浏览器,运行速度要快。是因为它占用了更多的内存空间,以空间换取了时间。
6、一维上的查找
先排序,再二分查找(前提要:有序)
hash 【O(1)】
双指针
参考博客
原文链接:https://blog.csdn.net/weixin_43716712/article/details/119923514