一、 算法的时间复杂度
1、如何评估算法的性能
数据结构和算法,本质上是解决现实存在的问题,即如何让解决指定问题的代码运行得更快?一个算法如果能在所要求的 资源限制(resource constraint)内将问题解决好,则称这个算法是有效率的(effient)。
限制可分为空间限制和时间限制。
如何衡量算法的执行时间? 可以使用如下的代码测试:
void calcDuration(){
long begin = System.currentTimeMillis();
function(n); // 测试function的执行时间
long end = System.currentTimeMillis();
}
我们可以通过end-begin来计算出function()的执行时间。这类“事后统计”的方法用来评估算法执行效率是正确的,但是它也有它的局限性:
- 受不同机器配置的影响,结果差异不同,甚至同一段算法,在不同机器上跑出来的结果差异很大。
- 算法的测试结果受输入的规模影响很大,也就是说,输入不同规模的数据,结果差别非常大。
能不能事先“算”出算法的执行效率呢?这就引出了以下两个概念:时间复杂度,空间复杂度。
2、 大O表示法
看下面的简单代码:
void foo(){
for(int i =0;i<n;i++){//执行了n次
System.out.println("Hello world");//执行了n次
}
System.out.println("function over");//执行了1次
}
在上述例子中,第1行的代码总共执行了n次,第2行的代码总共执行n次,第4行代码会执行1次。(这里假设计算机处理语句代价都为c)
假设算法总执行时间为T(n),其中n为问题的规模, 那么上述代码的总执行时间就是: T(n)=(2n+1)*c
可以看出,T(n)与代码的总执行次数总是成正比的,即与(2n+1)成正比. 我们将代码执行次数用f(n)来表示,即: f(n)=2n+1
有了执行次数后, 用大0 (英语:Big O notation)来表示算法的复杂度, 即:
T(n) =0(f(n))
针对上述代码的时间复杂度: T(n) =0(2n + 1) = 0(n)
在表示时间复杂度的时候, 大0并不是表示代码真正的执行时间, 而是表示代码执行时间随着数据规模增长的变化趋势. 一般具有以下特点:
- 不保留系数
- 只保留最高阶的项(随着数据规模n的增长, 低阶项对结果的影响越来越小, 可以省去. 常数可以看成0阶)
可以把大O符号想象成一个过滤性的漏斗,过滤那些系数以及低阶的项。
时间复杂度又分: 最优时间复杂度, 平均时间复杂度, 最坏时间复杂度. 一般平均复杂度更有指导意义.
二、排序算法的复杂度
1、冒泡算法时间复杂度计算
以冒泡算法为例, 我们算下冒泡算法的复杂度:
def bubbleSort(arr: Array[Int]): Unit = {
for (i <- 0 until arr.length - 1) { //
for (j <- 0 until arr.length - 1 - i) //
if (arr(j) > arr(j + 1)) {
swap(arr, j, j + 1)
}
}
}
}
最优(最初有序)情况: 每次都不需要交换, 则只需次数
三、其他排序算法时间复杂度