设计算法,必须了解如何计算算法的时间复杂度和空间复杂度。在实际问题中你才能估计当前算法还有多少可以优化的空间。
1、了解时间复杂度和空间复杂度。
时间复杂度:是指执行当前算法所消耗的时间。空间复杂度:是指执行当前算法需要占用多少内存空间。时间复杂度:
常用的几个时间复杂度:
O(1)< O(log(n)) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)一台计算机上运行所需要的时间。
不可能精确表示算法的时间复杂度——分为最坏情况、最好情况和平均情况。
算法效率的主要指标是基本操作次数的增长次数。为了将算法效率比较和归类,确定了3种符号:
上界:
p对于足够大的n,t(n)的上界由g(n)的常数倍来确定,即:t(n)<=Cg(n),记作t(n)=O(g(n))。
下界:
f(n)是某一算法的时间复杂性函数,g(n)是某一函数,当且仅当存在正的常数C和n0,使得对于所有的n>=n0,有f(n)≥Cg(n),记作f(n)= Ω(g(n))。
近似:
当且仅当存在正的常数C1和C2,使得对于所有的n>=n0,有 C1g(n)≤ f(n) ≤ C2g(n ),
记作: f(n)= Ө(g(n)) 。
例子:
解决时间复杂度的数学分析方法
基本操作通常是算法最内层循环中最费时的操作。
实际计算时间复杂度,我们会假设问题的规模充分大时(n趋近于无穷),算法在渐近意义上的阶。
算法时间效率分析方法主要由非递归分析法和递归式分析法两种。
1)分析非递归算法时间效率的通用方案
1、确定算法中作为输入规模的参数;2、找出算法的基本操作(通常位于算法的最内层循环中的操作);3、检查对于相同规模的不同输入实例,基本操作的执行次数是否可能不同,如果有,则需对最差效率、平均效率以及最优效率分别进行讨论;4、建立算法基本操作的执行频度的计算表达式;5、利用计算表达式的计算法则确定问题求解时间与问题规模的增长关系。非递归算法的分析比较简单,一般基本操作的执行次数很明显,可以直接写出其时间复杂度。
2)分析递归算法时间效率的通用方案
1、确定算法中作为输入规模的参数;2、找出算法的基本操作;3、检查对于相同规模的不同输入实例,基本操作的执行次数是否可能不同,如果有,则需对最差效率、平均效率以及最优效率分别进行讨论;4、针对算法基本操作的执行次数,建立与输入规模有关的递推关系式及其初始条件;5、求解递推式以确定问题求解时间与问题规模的增长关系。递归时间复杂度常用的解法:
分治法所满足的递归方程
问题规模为n的问题被分解成a个(n/b)个规模的子问题。f(n)是不参与递归部分的时间复杂度。
常用解法:(找出近似时间复杂度)
1) 代入法
【思路】:首先要对问题的时间复杂度做出预测,然后将预测带入原来的递归方程,如果没有出现矛盾,则是可能的解,最后用数学归纳法证明。
例子1:
有如下的递归问题:
。假设预测时间复杂度为
,不妨设 (其中k为常数)。将该结果带入方程中可得:方程左边
,方程右边 。当n区域无穷,由于
的阶高于 的阶,因而左右两边是相等的?你会发现
是始终成立的。你相当于发现一个下界(而且很可能不是很准确的下界)。假设
,带入上述方程:所以又找到了上界O(n^3)。结果还是不准确。
假设
,你会发现又发现一个下界,相对准确一点的下界。2)迭代法
例子:
递归地展开上述式子,可以得到此图。
得到:
得到趋近O(n^2)。
递归法一般规律总结:
对于
有如下递归:
相当于计算总共又多少结点,这个递归高度h是:logb(n)。宽度假设是a(几个子问题),则结点总共有:
所以,前面的T的时间复杂度计算出来了,需要估计f(n)的时间复杂度。
情况1:
此时如果f(n)的增长速度是多项式倍的慢于T的估计时间,则可得到:
情况2:
可近似等于:
即:
则有:
情况3:
若f(n)增长的比前一项快很多,则有:
其他一般规律:
3、递推式是一个无穷序列幂级数的递归算法时间复杂度分析方法
例如斐波那契数列:
T(n)=T(n-1)+T(n-2)。
母函数法和特征方程法等等,后续更新,需要一些数学基础。
计算较简单的空间复杂度:
一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。
1)固定部分:这部分空间的大小与输入/输出的数据的个数多少、数值无关,主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间,这部分属于静态空间。
2)可变空间:这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等,这部分的空间大小与算法有关。
算法的空间复杂度并不是计算实际占用的空间,而是计算整个算法的辅助空间单元的个数。(相当于衡量动态分配的空间)。
辅助变量是算法运行过程中临时占用的存储空间,与空间复杂度相关。
栗子:
1)斐波那契数列:
public static int f1(int n) { if(n < 1) { return 0; }else if(n == 1 || n == 2) { return 1; } return f1(n-1) + f1(n-2);斐波那契数列是由当前数的前两个数相加得到当前数。
你会发现随着你要求的数n的增大,计算机需要动态存储的前面的数也要增大,我要求5,那么需要求出前4个数占用了内存空间。以此递增,所以空间复杂度为O(n)。
2)二分查找法
要求数据必须的排好序的,每次以中间的值进行比较,根据比较的结果可以直接舍去一半的值,直至全部找完(可能会找不到)或者找到数据为止。
while(top <= end){ mid = (top + end) / 2; if(number[mid] == key){//如果找到,直接跳出循环 find = mid; break; }else if(number[mid] > key){//如果当前值比需要找的值大,就将尾部的下标移至mid的前一处 end = mid - 1; }else{//如果当前值比需要找的值小,就将头部的下标移至mid的后一处 top = mid + 1; } }你会发现二分查找一直是在一个数组。因为整个运算过程没有空间的改变,所以空间复杂度为O(1)。