一、data analysis
已知几组数据:输入大小和运行时间,采用lg-lg plot 可以将输入和输出进行线性拟合,最终得到运行时间和输入大小的函数表达式,也称作power law,有了这个表达式,我们可预测任意大小的输入所对应的运行时间。
二、doubling hypothesis
除了使用上述的拟合方法外,从已经推出的power law出发,还有一个更简便的方法:由于所有的算法都符合power law ,当对T(N)和T(2N)的比率进行分析时,可发现比率值取lg后就是T(N)表达式中的b。若希望求出a,可代入一对具体的数值。
T(N)的表达式中,有两个参数:a和b,b代表算法和输入数据,与计算机无关;而a受算法、输入数据、系统的影响。
三、mathematical model
total running time=操作代价*频率,频率取决于算法和输入数据。一些基本的操作的cost通常是常数:
接下来分析操作执行的频率:
简化数学模型:
1、cost model:由于执行基本操作的cost都是常数,因此分析程序的运行时间时,我们可以使用执行频率最高的操作所花费时间来近似程序的运行时间。
2、tilde notation:分析运行时间,我们更关注输入很大的情况,因此,我们可以利用极限来简化操作执行的频率。
根据算法的运行时间的增长阶数可以对算法进行分类:const、logarithmic、linear、linearithmic、quadratic、cubic、exponential,根据log-log图可以看出,const、logarithmic几乎不随输入的变化而变化、linear、linearithmic与输入成正比变化,这几类算法被认为能适应输入规模,可以在大型输入问题中被使用。而quadratic、cubic、exponential在大型输入问题中,性能很差,最好别用!
分析二分查找法的时间复杂度,step1:建立cost model,操作代价最多的动作是比较key和中间值(涉及访问数组),因此我们需要计算出这个操作的执行频次,核心思路是在查找的过程中不断将区间减半(模型可利用递归式来表示),因此可得出下图中的递推式(精巧之处!)。最终可得出比较指向的频次为1+lgN。算法属于logarithmic类型,性能很好!
考虑一个基本的问题:3-sum,存在多少个组合方式其和为0。
basic:使用暴力破解法,三重循环,找出所有可能的组合方式,判断和是否为0。时间复杂度为N3,不适用于大型问题的求解。
改进:将增长阶数变为quadratic 。难道一定要取出三个数求和吗?难道不能取出两个数求出其和后在数组中进行查找吗,通过利用二分查找法,将实现从死硬的三重循环变为二重循环和二分查找法。从三阶变为二阶,性能得到了很大的改善!
同一种算法面对不同的输入性能也是各不相同!因此分析算法运行时间,可以从不同的角度出发:最好的运行时间、最差的运行时间、平均运行时间。
对于实际需要解决的问题,可以有不同的算法、不同的输入,面对待解决的问题,我们希望知道这个问题所对应的最优算法!为了便于评估,比较各个算法时我们使用最长的运行时间!如何确定问题的上限呢?与确切的算法相关,不断尝试新的算法来降低运行时间。问题难度的下线,是可能最好的情况。当我们所使用的上限等于下线时,上限所对应的具体的算法就是最优解法!
在描述问题复杂度或是证明问题的最优算法的运行时间时,理论上给出了三种notation:分别用来描述算法运行时间(growth of order)、表示下限、表示上限
有两个例子:求解1-sum问题的最优算法和求解3-sum问题的最优算法。
3sum:
面对要求解的问题,分析出下限,不断尝试新的算法来逼近下限,直到相等。
内存分析:Java 实现中,基本类型、数组、对象、引用所使用的内存空间。同样可使用tlide notation,忽视不关键的部分。
分析算法性能时,结合数学模型和经验模型!