算法时间复杂度分析
大O记法
定义:
在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随着n的变化情况并确定T(n)的量级。算法的时间复杂度,就是算法时间的量度,记做:T(n) = O(f(n))。它表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度,其中f(n)是问题规模n的某个函数。
用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。
使用大O记法表示算法的时间复杂度的几个规则:
1、用常数1取代运行时间中的所有加法常数;
public static void main(String[] args) {
//计算1 ~ 100的和
int n = 100; //1次运算
method1(n);
}
private static void method1(int n) {
int sum = (1 + n) * n / 2; //1次运算
System.out.println(sum);
}
一共执行2次运算,由规则1可知,时间复杂度为 O(1);
2、在修改后的运行次数中,只保留最高阶项;
public static void main(String[] args) {
//计算1 ~ 100的和
int n = 100; //一次运算
method2(n);
}
private static void method2(int n) {
int sum = 0; //一次运算
for (int i = 1; i <= n; i++) {
sum += i;
} //n次运算
System.out.println(sum);
}
一共进行n + 2次运算,先根据规则1,得到n + 1,再根据规则2,得到时间复杂度为:O(n)
3、如果最高阶存在,且与之相乘的常数不为1,则去除与这个项相乘的常数;
public static void main(String[] args) {
int n = 100; //一次运算
int sum = 0; //一次运算
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
sum += j;
} //n次运算
} //n次运算
System.out.println(sum);
}
一共运算n^2 + 2次,先根据规则1,再根据规则2,再根据规则3,得出时间复杂度为O(n^2);
常见的大O阶
1、线性阶
**一般含有非嵌套循环涉及线性阶,线性阶就是随着输入规模的扩大,对应计算次数成直线增长,**例如:
public static void main(String[] args) {
int n = 100;
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
System.out.println(sum);
}
上面这段代码,它的循环的时间复杂度为O(n),根据规则以及循环体中的代码的执行次数进行判断
2、平方阶
一般为嵌套循环
public static void main(String[] args) {
int n = 100;
int sum = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
sum += j;
}
}
System.out.println(sum);
}
n = 100,也就是说,外层循环执行一次,内层循环执行100次,那么总程序一共需要执行100 * 100次,也就是n2次,所以这段代码的**时间复杂度为O(n2)**
3、立方阶
一般为三层嵌套循环
public static void main(String[] args) {
int n = 100;
int sum = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for(int k = 0; k < n; k++){
sum += k;
}
}
}
System.out.println(sum);
}
同理,程序一共要执行 100 * 100 * 100次,所以时间复杂度为O(n^3)
4、对数阶
public static void main(String[] args) {
int n = 100;
int i = 1;
while(i < 100){
i *= 2;
}
}
由于每次i * 2之后,值逐渐变大,假设有x个2相乘后大于n,退出循环,得2^x = n, 既x = log(2)n,所以时间复杂度为O(logn)
对于对数阶,由于随着输入规模n的增大,不管底数为多少,他们的增长趋势是一样的,所以我们会忽略底数。
常见时间复杂度总结
描述 | 增长的数量级 | 说明 | 举例 |
---|---|---|---|
常数级别 | 1 | 普通语句 | 两数求和 |
对数级别 | logN | 二分策略 | 二分查找 |
线性级别 | N | 循环 | 求和,遍历查找 |
线性对数级别 | NlogN | 分支思想 | 归并排序 |
平方级别 | N^2 | 双层循环 | 二维数组遍历 |
立方级别 | N^3 | 三层循环 | 检查所有三元组 |
指数级别 | 2^N | 穷举查找 | 检查所有子集 |
时间复杂度排序
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3)
在优化算法时,尽可能的追求的是O(1),O(logn),O(n),O(nlogn)这几种时间复杂度,而如果发现算法的时间复杂度为平方阶、立方阶或者更复杂的,那么我们就认为这种算法时不可取的,需要优化