一 前言
如果对本系列产生什么疑问的话, 建议先下前言, 里面有我的联系方式, 教材的下载地址, 特殊词语的规定之类的........ 链接地址: https://blog.csdn.net/qq_41057280/article/details/89209081 ; 最后, 以下说法仅为个人理解, 如有错误, 欢迎教正
二 介绍
程序语句重复执行的次数作为程序的时间量度。时间复杂度则是描述该时间量度的函数. 如果通俗点解释的话, 时间复杂度就是程序语句的执行次数的函数表达式。
时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。
三、渐近时间复杂度
时间复杂度虽然可以表述为执行次数的函数表达式,但是不代表直接用该表达式进行算法效率的比较。因为一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。
不过,一个算法运行不只包含算法本身,还有维持该算法运行的其他语句,但比较算法优劣的时候不应该将其包括在内。 因此,计算时间复杂度时,一般将其排除。也就是排除常数项
并且,在程序中,不同的输入值,运行次数不同,可能产生不同的比较结果。因此我们通常使用算法的最坏情况复杂度(一般是无穷大)来进行比对。
所以,我们应该比较的是时间复杂度的增长率(因为无穷大无法计算)。 举个例子, O(n)与 O(n^2)一眼就能看出, O(n^2)的时间复杂度大。 但是O(n)与O(n+1)呢?在无穷大的时候,这两者的差距是忽略不计的,可以写作O(n)。这便是渐近时间复杂度。
四、计算方法
原理说完了,下面总结下该如何计算。
- 找出算法的具体实现语句
- 计算该语句的基础数量级
- 只保留最高项(不小于1)
- 去掉常数项和最高项的系数
- 如果不存在最高项(不小于1),计作O(1)
- 用大O表示该表达式
五、计算示例
为了方便理解,我这边写几个简单的例子。下列示例的参数都是代表问题规模n, 每行后面的注释代表当前行的执行次数
1、时间复杂度: O(1)
执行次数2次
因为,如果不存在最高项(不小于1),只能计作O(1)
所以时间复杂度为O(1)
public void test1(int n) {
int x = n + 1; // 1
System.out.println(x); // 1
}
2、时间复杂度: O(n)
执行次数为 (n+1) + n = 2n+1
去掉常数项和最高项的系数
所以时间复杂度为O(n)
public void test2(int n) {
for (int i=0; i<n; i++) { // n+1
System.err.println(i); // n
}
}
3、时间复杂度: O(n²)
执行次数: (n+1) + n(n+1) + n² = 2n² + 2n + 1
只保留最高项(不小于1),去掉常数项和最高项的系数
所以时间复杂度为O(n²)
public void test3(int n) {
for (int i=0; i<n; i++) { // n+1
for (int j=0; j<n; j++) { // n(n+1)
System.out.println(i + ":" + j); // n²
}
}
}
如果不懂for循环执行次数为什么加1,那请你回头看看for循环的原理。
时间复杂度的基本计算规则大抵就是这些。
说到底,可以不用标注其余语句的执行次数,只标注代码的运算主体即可(也就是上面的system.out输出语句)。在绝大多数情况下,运算主体的执行次数就是算法的时间复杂度
下面的例子也就不再对全部语句进行标注
4、 O(logn)
logn = log₂n
这里的执行主体是 i *= 2; 可以推出等式 2的i次方 <= n, 即 i = logn = log₂n
所以时间复杂度为 O(logn)
public void test4(int n) {
int i = 1;
while(i< n) {
i *= 2;
}
}
5、O(logₖn)
同理可得,下列算法的时间复杂度为O(logₖn)
public void test5(int n, int k) {
int i = 1;
while(i< n) {
i *= k;
}
}
6、O(n)
这是一段递归算阶乘的程序, 其总运算次数为 n。所以时间复杂度为O(n)
public int test6(int n) {
if (n <= 1) return 1;
return n * test6(n -1);
}
7、O(nlogn)
public void test7(int n) {
for (int i=0;i<n; i*=2) { // 注意这里, i *= 2
for (int j=0; j<n; j++) {
System.out.println(i + ":" + j);
}
}
}
8、O(√n)
运算过程为 1+2+……+i >= n, 用等差数量的求和公式, i(1 + i)/2 >= n
所以, 时间复杂度为O(√n)
public void test8(int n) {
int i = 0;
int sum = 0;
while(sum < n) {
sum += ++i; // ++i => i=i+1, 自增之后再运算
}
}
9、O(³√n)
i*i*i > n => i > ³√n
所以, 时间复杂度为O(³√n)
public void test9(int n) {
for (int i=1; i<=n; i = i*i*i) {
System.err.println(i);
}
}
六、总结
时间复杂度一般求的是最坏时间复杂度,即n为无穷大时。为了方便对比,采用渐近时间复杂度进行表示,也就是大O。
一般来讲,时间复杂度有下列基准进行对比:
c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n!
c为常数, 时间复杂度越大,代表代码执行效率越低。