一、为什么需要分析时间复杂度
-
测试结果依赖测试环境:
假设你要做一个汤。你在一个高压锅中煮,20分钟就完成了。但是如果你用一个普通的锅,可能需要1个小时。这就像你使用不同的计算机来运行算法:高效的计算机(高压锅)运行得更快,而较慢的计算机(普通锅)需要更长的时间。
因此,仅凭实际运行时间来评价算法的优劣是不准确的。
-
测试结果受数据规模的影响很大:
还是那道汤,假如你要为自己煮,可能需要的材料很少,20分钟就可以搞定。但是如果你要为一个大家庭或一个聚会煮,需要的材料就会多得多,可能需要几个小时。
这就像一个算法处理不同大小的数据集。对于小数据集,它可能非常迅速;但当数据规模扩大,它可能就不再那么高效了。
因此,我们分析时间复杂度,就像一个厨师在烹饪前先估算所需的时间,考虑到他所使用的工具和准备为多少人做饭。这样,不管面对什么情况,他都能给出一个合理的预测,确保食物及时上桌。
二、时间复杂度的概念
跑步比赛的时候,当我们要比较哪位运动员跑得更快时,我们通常会看他们完成赛跑的时间。同样地,当我们要比较哪个算法“跑得更快”,我们会看其时间复杂度。这是衡量算法速度的方法。
三、大O复杂度表示法
以下是一段很简单的代码,正常人不会这么写,这里我用来演示。
String niceSong(int n) {
String song = "la "; // 前奏(第2行)
int i = 0;
for (; i < n; i++) {
song += "xi~"; // 每次弹奏一个“xi”音符
}
return song;
}
我们最后输出一段音乐旋律“la xi~xi~xi~xi~xi~”。假设每行代码的执行时间都一样,为unit_time(单位时间)。那么这段代码第2、3行都运行了1遍,所以需要2n*unit_time的执行时间,第4、5行都运行了n遍,所以需要2n*unit_time 的执行时间,所以这段代码的总执行时间是(2n+2)*unit_time。不难发现,所有代码的执行时间 T(n) 与每行代码的执行次数成正比。
我们可以把这个规律总结成一个公式:
T(n):代码的执行时间。其中,T 代表“时间”, n 代表着算法的输入大小,在上面这段代码中,n是“xi”这个音符的弹奏次数。
O():这是所谓的“大O表示法”,它用于描述算法复杂度的上界。当我们说一个算法的时间复杂度为 O(f(n)) 时,我们指的是随着输入大小 n 的增长,算法的执行时间将不超过 f(n) 的某个常数倍。
f(n):这是一个函数,表示随着输入大小 n 的变化,例如,f(n)=n 表示线性的增长;表示算法时间会以平方的速度增长。
那么以上代码中的时间复杂度就可以表示为:,这就是大 O 时间复杂度表示法。现在,关于大 O 表示法:你可以想象它就像是描述乐曲的旋律时,我们只关心高潮(最大)部分。在刚刚的乐曲代码中,当n很大时,公式中的低阶、常量、系数三部分并不影响左右增长趋势,所以都可以忽略。
我们只需要记录一个最大量级就可以了,那么 就可以简化为
。同理,
可以简化为
。
需要注意的是:大 O 时间复杂度并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势。