代码的执行总时间 T(n)和每行代码的执行次数 n 之间的关系:T(n)=O(f(n))
公式中的 O 表示代码的执行总时间 T(n)和其执行总次数 f(n)成正比。这种表示法,称之为大 O 记法。大 O 记法 T(n)=O(f(n)),表示随问题规模 n 的增大,算法执行时间的增长率和 f(n)的增长率相同,表示的是算法的渐近时间复杂度,简称时间复杂度。
几个需要关注的结论:
加法常数项可以忽略,如:
O(2n + 1) == O(2)
除去最高次项,其它次项可以忽略,如:O(n² + 2n + 1) == O(n²)
与最高次项相乘的常数可以忽略,如:O(3n²) == O(n²)
常数阶 O(1)
public void sum(int n) {
int sum = 0; // 执行一次
sum = n*2; // 执行一次
System.out.println(sum); // 执行一次
}
上面代码一共执行 3 次,用大 O 表示法表示应该为:T(n)=O(3),但是大 O 表示法有一个基本法则:用常数 1 取代运行时间中的所有加法常数。 因此,这段代码的时间复杂度是T(n)=O(1),只要执行时间固定就是 O(1)。
对数阶 O(logn)
public void logarithm(int n) {
int count = 1; // 执行一次
while (count <= n) { // 执行logn次
count = count*2; // 执行logn次
}
}
x 个 2 相乘后其结果值会大于 n 则停止运行,即 2^x=n 。由 2^x=n 可以得到 x=logn,所以这段代码时间复杂度是 O(logn)。
线性阶 O(n)
public void circle(int n) {
for(int i = 0; i < n; i++) { // 执行n次
System.out.println(i); // 执行n次
}
}
线性阶表示代码要执行 n 次,如下 for 循环中的代码,第二行和第三行代码都执行 n 次,即 f(n)=2n。根据前面的分析,与最高次项相乘的常数 2 是可以忽略的,因此这段代码的时间复杂度是 O(n)。
线性对数阶 O(nlogn)
public void logarithm(int n) {
int count = 1;
for(int i = 0; i < n; i++) { // 执行n次
while (count <= n) { // 执行logn次
count = count*2; // 执行nlogn次
}
}
}
线性对数阶 O(nlogn)就是将一段时间复杂度为 O(logn)的代码执行 n 次
平方阶 O(n²)
public void square(int n) {
for(int i = 0; i < n; i++){ // 执行n次
for(int j = 0; j <n; j++) { // 执行n次
System.out.println(i+j); // 执行n方次
}
}
}
如下代码是个双重 for 循环,其内循环的时间复杂度是线性阶 O(n)。对于外循环来说,是将内循环这个时间复杂度为 O(n)代码在执行 n 次,所以整个这段代码的时间复杂度为 O(n²)。
当内层循环和外层循环的次数不一致时,时间复杂度又该怎么表示呢?如下,内层循环执行 m 次,其时间复杂度为 O(m),外层循环执行次数为 n 次,其时间复杂度为 O(m)。整段代码的时间复杂度是就是 O(m*n),即循环的时间复杂度等于循环体的时间复杂度乘以该循环运行次数。
public void square(int n, int m) {
for(int i = 0; i < n; i++){ // 执行n次
for(int j = 0; j <m; j++) { // 执行m次
System.out.println(i+j); // 执行mn次
}
}
}