时间频率
一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。
一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
时间复杂度
在时间频度中,n称为问题的规模。当n不断变化时,时间频度 T(n) 也会不断变化。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度概念。
一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
常数时间
若对于一个算法,T(n) 的上界与输入大小无关,则称其具有常数时间,记作 O(1) 时间。一个例子是访问数组中的单个元素,因为访问它只需要一条指令。
虽然被称为“常数时间”,运行时间本身并不必须与问题规模无关,但它的上界必须是与问题规模无关的确定值。举例,“如果a > b则交换a、b的值”这项操作,尽管具体时间会取决于条件“a > b”是否满足,但它依然是常数时间,因为存在一个常量t使得所需时间总不超过t。
下面的代码就是常数时间的。
printf("Hello, World!\n");
if (3 > 2) {
printf("1\n");
} else {
printf("2\n");
}
对数时间
若算法的T(n) =O(logn),则称其具有对数时间。由于计算机使用二进制的记数系统,对数常常以2为底(即,有时写作lgn)。O(logn) 不论对数的底是多少,是对数时间算法的标准记法。
常见的具有对数时间的算法有二叉树的相关操作和二分搜索。对数时间的算法是非常有效的,因为每增加一个输入,其所需要的额外计算时间会变小。
下面一个简单例子,使用递归将字符串砍半并且输出。这样的例子时间常数就是对数。
function binarySearch(arr, l, r, target) {
if (l < r){
return -1;
}
let mid = l + (r-l)/2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
return binarySearch(arr, l , mid-1, target);
} else {
return binarySearch(arr, mid+1, r, target);
}
}
线性时间
如果一个算法的时间复杂度为O(n),则称这个算法具有线性时间,或O(n)时间。
void aFunc(int n) {
for(int i = 0; i < n; i++) { // 循环次数为 n
printf("Hello, World!\n"); // 循环体时间复杂度为 O(1)
}
}
线性对数时间
若一个算法时间复杂度T(n) = O(nlog n),则称这个算法具有线性对数时间。因此,从其表达式我们也可以看到,线性对数时间增长得比线性时间要快,但是对于任何含有n,且n的幂指数大于1的多项式时间来说,线性对数时间却增长得慢。
比如排序算法中的归并排序就是线性对数时间。
平方时间
f(n)=n³时,时间复杂度为,可以称为平方阶。
void aFunc(int n) {
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
printf("Hello World\n");
}
}
}
当 i = 0 时,内循环执行 n 次运算,当 i = 1 时,内循环执行 n - 1 次运算……当 i = n - 1 时,内循环执行 1 次运算。所以,执行次数 T(n) = n + (n - 1) + (n - 2)……+ 1 = n(n + 1) / 2 = n^2 / 2 + n / 2。
指数时间
f(n)=2ⁿ时,时间复杂度为,可以称为指数阶。 如下面的多次递归调用
function f(n) {
if (n==0) {
return 1;
}
return f(n-1) + f(n-1)
}
阶乘时间
f(n)=n!时,时间复杂度为O(n!),可以称为阶乘阶。旅行商问题问题就是这个复杂度。
时间复杂度总结
如上图所示,常见的算法时间复杂由小到大依次为:
数据规模概念
测试代码
我们利用 Python 来一个简单的数据测试。测试代码如下:
# -*- coding:utf-8 -*-
"""
这是一个关于次幂计算的函数
"""
import time
def Power():
for i in range(1, 10):
n = pow(10, i) # 计算10的i次幂
start_time = time.time()
sum(x for x in range(1, n+1))
end_time = time.time()
print("10^%d:%fs"%(i, (end_time-start_time)))
Power()
测试结果
硬件环境 | 软件环境 | Python版本 | 运行结果 |
i7-8700K 3.70GHz 16GB内存 | Win10企业版 64位 | Python 3.7 | ![]() |
Intel(R) Xeon(R) Cascadelake CPU @ 2.50GHz | Ubuntu 18.04.3 LTS 64位 | Python 3.6 | ![]() |
Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz | Ubuntu 16.04.6 LTS 64位 | Python 3.5 | ![]() |
经验数据
如果要想在1s之内解决问题:
- O(n) 的算法可以处理大约
级别的数据。
- O(nlogn) 的算法可以处理大约
级别的数据。
的算法可以处理大约
级别的数据。