- 时间频度:一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道.但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了.并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多.一个算法中的语句执行次数称为语句频度或时间频度.记为T(n).
- 时间复杂度的定义:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级(根据输入规模n估计执行次数).算法的时间复杂度也就是算法的时间量度,记作"T(n)=O(f(n))",它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称为算法的渐近时间复杂度,也叫作时间复杂度,其中f(n)是问题规模n的某个函数(执行次数==时间)
- 这样用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法
-
如何分析一个算法的时间复杂度呢?即推导大O阶攻略
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行次数函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数
- 得到的最后结果就是大O阶
-
例子:常数阶
long sum = 0 , n = 100_000_000; //1
System.out.println("1");
System.out.println("1");
System.out.println("1");
System.out.println("1");
System.out.println("1");
System.out.println("1");
System.out.println("1");
System.out.println("1");
sum = (1+n) * n / 2; //1
- 根据上面定义的概念:T(n)是关于问题规模n的函数,那么这段代码是也是关于n的一个函数,输出语句并不会因为n的改变而改变,所以直接忽略输出语句,最后记作为
O(1),并且上面有说明: 用常数1取代运行时间中的所有加法常数,所以在这段代码中实则是执行次数为1+1=2,用常数1代替加法,那么最后也是O(1)
- 上面有十条语句,有些人想:不应该是O(10)吗?这里需要注意的是,不管你的常数是多少一律写为1,并且在分支结构情况下,分支结构并不会因为n的变化而执行次数增多,所以复杂度也是O(1)
-
例子:线性阶
long sum = 0 , n = 100_000_000;
for (long i = 0; i <= n; i++) {
sum += i;
}
- 一般含有非嵌套循环涉及线性阶,线性阶就是随着输入规模n的扩大,对应计算次数呈直线增长,在这段代码中,循环随着n的增加而增加,所以它的时间复杂度为O(n)
-
例子:平方阶
int x = 0, sum = 0 , n = 100;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <=n ; j++) {
sum += 1;
}
}
- 上面口算得知执行次数为100*100=1w次,而n=100,执行此时就是n^2,所以这的时间复杂度为O(n^2)
- 当然如果有三个嵌套循环,他的时间复杂度就为O(n^3)次方了,所以我们就可以总结出:循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数
-
例子:平方阶
int x = 0, sum = 0 , n = 100;
for (int i = 1; i <= n; i++) {
for (int j = i; j <=n ; j++) {
sum += 1;
}
}
- 如上内循环的初始化条件不一样了,它是以外层循环的i的变量的值作为初始值的,他的执行次数计算就为:100+(100-1)+(100-2) = n+(n-1)+(n-2).我们观察这个式子,这不就是本文刚开头的从一加到n的计算方式嘛,所以它也可以用优化算法:n(n+1)/2来计算,n(n+1)/2 = (n^2+n)/2 = n^2/2+n/2,根据之前的攻略,首先没有常数相加,然后看第二条,只保留最高阶项,n^2最高,所以去掉n/2,第三条,去除与最高项相乘的常数,最终得O(n^2)
-
例子:对数阶
int i = 1 ,n = 100;
while (i < n){
i *= 2;
}
- 由于每次i*2之后,就离着n更近了,假设有x个2相乘后>=n,则循环退出,于是由2^x=n得到x=log(2)n,所以这个循环的时间复杂度为0(logn)
- 在数学中,log对数是对求幂的逆运算,logA(N)就等于A的多少次方等于N
-
函数调用的时间复杂度分析
public void test() {
int n = 100;
for (int i = 0; i < n; i++) {
function(i);
}
}
private void function(int i) {
System.out.println(i);
}
- 如上function函数只有一个输出语句,那么他的时间复杂度是O(1),在test中循环调用function方法,那么function的整体复杂度就为O(n)
-
如果把function改为如下呢?
private void function(int i) {
for (int j = 0; j < i; j++) {
System.out.println(j);
}
}
- 因为在test方法中调用function,function中还有一个循环,所以这的嵌套循环跟上面的第二个平方阶例子相似,所以时间复杂度为O(n^2)
-
例子
n++; //1
function(n); //n
for (int i = 0; i < n ; i++ ) { //n^2
function(i);
}
for (int i = 0; i < n ; i++ ) { //n^3
for (int j = 0; j < n ; i++ ) {
function(j);
}
}
- 如上代码调用的function函数是前面改动后带循环的function函数,代码中已经表明执行次数,n++一次,直接调用function(n)是执行n次,因为里面就一个根据n的循环,下面第一个for循环中调用function函数,整体构成了一个嵌套for,所以它的执行次数是n^2,再下面是一个三个嵌套for,那么就是n^3,所以总的执行次数为:n^2+n^3+n+1,首先忽略常数,就变为n^2+n^3,然后取最高阶项,那么就是n^3,所以整个时间复杂度为O(n^3)