时间复杂度

时间频率

一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。

一个算法中的语句执行次数称为语句频度或时间频度。记为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为底(即log_{2}n,有时写作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³时,时间复杂度为\small O(n^{2}),可以称为平方阶。

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ⁿ时,时间复杂度为\small O(2^{n}),可以称为指数阶。 如下面的多次递归调用

function f(n) {
    if (n==0) {
        return 1;
    }
    return f(n-1) + f(n-1)
}

阶乘时间

f(n)=n!时,时间复杂度为O(n!),可以称为阶乘阶。旅行商问题问题就是这个复杂度。

时间复杂度总结

如上图所示,常见的算法时间复杂由小到大依次为:

\small O(1)<O(logn)<O(n)<O(nlogn)<O(n^{2})<O(n^3)<\cdots <O(2^{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.50GHzUbuntu 18.04.3 LTS 64位Python 3.6
Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHzUbuntu 16.04.6 LTS 64位Python 3.5

经验数据

如果要想在1s之内解决问题:

  • O(n) 的算法可以处理大约 10^{7} 级别的数据。
  • O(nlogn) 的算法可以处理大约10^{6}级别的数据。
  • O(n^{2}) 的算法可以处理大约 10^{4} 级别的数据。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的老周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值