大O表示法
用O(n)来体现算法时间复杂度的记法被称作大O表示法。一般我们我们评估一个算法都是直接评估它的最坏的复杂度。
推导大O阶
推导大O阶有一下三种规则:
- 用常数1取代运行时间中的所有加法常数
- 只保留最高阶项
- 去除最高阶的常数
常见算法复杂度样例
常数阶
int sum = 0, n = 10; // 语句执行一次
int sum = (1+n)*n/2; // 语句执行一次
cout << "The sum is : " << sum << endl; //语句执行一次
这样的一段代码它的执行次数为 3 ,然后我们套用规则1,则这个算法的时间复杂度为 O(1),也就是常数阶。
x=91;
y=100;
while (y>0) {
if (x>100) {
x=x-10;
y–;
} else {
x++;
}
}
如上的代码看上去很大,实际的 T(n) = O(1) 。因为总共循环运行了 1000 次,但是这段程序的运行是和 n 无关的,所以只是一个常数阶的。
对数阶
int number = 1; // 语句执行一次
while (number < n) { // 语句执行logn次
number *= 2; // 语句执行logn次
}
上面的算法中,number 每次都放大两倍,我们假设这个循环体执行了 m 次,那么
即 m = logn
,所以整段代码执行次数为 1 + 2*logn,则 f(n) = logn
,时间复杂度为 O(logn)。
线性阶
int i = 0; // 语句执行一次
while (i < n) { // 语句执行n次
cout << "Current i is " << i << endl; //语句执行n次
i++; // 语句执行n次
}
这个算法中代码总共执行了 n 次,根据规则 2,因此该算法的时间复杂度是 O(n)。
线性对数阶
for (int m=1; m<n; m++) {
int i = 1;
while (i<n) {
i = i * 2;
}
}
该算法的时间复杂度是 O(nlogn)。
平方阶
for (int i = 0; i < n; i++) { // 语句执行n次
for (int j = 0; j < n; j++) { // 语句执行n^2次
cout << "I am here!" << endl; // 语句执行n^2
}
}
上面的嵌套循环中,代码共执行
,则
。所以该算法的时间复杂度为
。
立方阶
for (int i = 0; i < n; i++) { // 语句执行n次
for (int j = 0; j < n; j++) { // 语句执行n^2次
for (int k = 0; k < n; k++) { // 语句执行n^3次
cout << "I am here!" << endl; // 语句执行n^3
}
}
}
上面的嵌套循环中,代码共执行
,则
。所以该算法的时间复杂度为
。
指数阶
function f(n) {
if (n==0) {
return 1;
}
return f(n-1) + f(n-1)
}
如果 n=0,f(n)执行为 1 次。
如果 n=1,f(n)执行了 f(0)+f(0)=2 次。
如果 n=2,f(n)执行了 f(1)+f(1)=f(0)+f(0)=4 次。
如果 n=3,f(n)执行了 f(2)+f(2)=f(1)+f(1)=8 次。
如果 n=4,f(n)执行了 f(3)+f(3)=f(2)+f(2)=16 次。
以此类推。当 n 趋向无穷大的时候,我们可以推导出
。所以该算法的时间复杂度为
。类似的斐波那契数列的时间复杂度也是
。下面借用一个别人绘制的图片说明。
阶乘阶
待举例。
这类算法的时间复杂度为
。
总结
常见时间复杂度的比较
O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ) < O(n!)