摘要
本文论述了在算法分析领域一个重要问题——时间复杂度分析的基础内容。本文将首先明确时间复杂度的意义,然后以形式化方式论述其在数学上的定义及相关推导。从而帮助你们从本质上认清这个概念。
前言
一般,对于一个给定的算法,咱们要作 两项分析。第一是从数学上证实算法的正确性,这一步主要用到形式化证实的方法及相关推理模式,如循环不变式、数学概括法等。而在证实算法是正确的基础上,第二部就是分析算法的时间复杂度。算法的时间复杂度反映了程序执行时间随输入规模增加而增加的量级,在很大程度上能很好反映出算法的优劣与否。所以,做为程序员,掌握基本的算法时间复杂度分析方法是颇有必要的。
可是不少朋友并不能清晰的理解这一律念,究其缘由,主要是由于没有从数学层面上理解其本质,而是习惯于从直观理解。下面,咱们就一步步走近算法时间复杂度的数学本质。
算法时间复杂度的数学意义
从数学上定义,给定算法A,若是存在函数F(n),当n=k时,F(k)表示算法A在输入规模为k的状况下的运行时间,则称F(n)为算法A的时间复杂度。
这里咱们首先要明确输入规模的概念。关于输入规模,不是很好下定义,非严格的讲,输入规模是指算法A所接受输入的天然独立体的大小。例如,对于排序算法来讲,输入规模通常就是待排序元素的个数,而对于求两个同型方阵乘积的算法,输入规模能够看做是单个方阵的维数。为了简单起见,在下面的讨论中,咱们老是假设算法的输入规模是用大于零的整数表示的,即n=1,2,3,……,k,……
咱们还知道,对于同一个算法,每次执行的时间不只取决于输入规模,还取决于输入的特性和具体的硬件环境在某次执行时的状态。因此想要获得一个统一精确的F(n)是不可能的。为了解决这个问题,咱们作一下两个说明:
1.忽略硬件及环境因素,假设每次执行时硬件条件和环境条件是彻底一致的。
2.对于输入特性的差别,咱们将从数学上进行精确分析并带入函数解析式。
算法时间复杂度分析示例
为了便于朋友们理解,我将不会采用教科书上惯用的快速排序、合并排序等经典示例进行分析,而是使用一个十分简单的算法做为示例。咱们先来定义问题。
问题定义:
输入——此问题输入为一个有序序列,其元素个数为n,n为大于零的整数。序列中的元素为从1到n这n个整数,但其顺序为彻底随机。
输出——元素n所在的位置。(第一个元素位置为1)
这个问题很是简单,下面直接给出其解决算法之一(伪代码):
LocationN(A)
{
for(int i=1;i<=n;i++)-----------------------t1
{
if(A[i] == n) ----------------------------t2
{ return i; }------------------------t3
}
}
咱们来看看这个算法。其中t一、t2和t3分别表示此行代码执行一次须要的时间。
首先,输入规模n是影响算法执行时间的因素之一。在n固定的状况下,不一样的输入序列也会影响其执行时间。最好状况下,n就排在序列的第一个位置,那么此时的运行时间为“t1+t2+t3”。最坏状况下,n排在序列最后一位,则运行时间为“n*t1+n*t2+t3=(t1+t2)*n+t3”。能够看到,最好状况下运行时间是一个常数,而最坏状况下运行时间是输入规模的线性函数。那么,平均状况如何呢?
问题定义说输入序列彻底随机,即n出如今1...n这n个位置上是等可能的,即几率均为1/n。而平均状况下的执行次数即为执行次数的数学指望,其解为:
E
= p(n=1)*1+p(n=2)*2+...+p(n=n)*n
= (1/n)*(1+2+...+n)
= (1/n)*((n/2)*(1+n))
= (n+1)/2
即在平均状况下for循环要执行(n+1)/2次,则平均运行时间为“(t1+t2)*(n+1)/2+t3”。
由此咱们得出分析结论:
t1+t2+t3 <= F(n) <= (t1+t2)*n+t3,在平均状况下F(n) = (t1+t2)*(n+1)/2+t3
算法的渐近时间复杂度
以上分析,咱们对算法的时间复杂度F(n)进行了精确分析。可是,不少时候,咱们不须要进行如此精确的分析,缘由有下:
1.在较复杂的算法中,进行精确分析是很是复杂的。
2.实际上,大多数时候咱们并不关心F(n)的精确度量,而只是关心其量级。
基于此,提出渐近时间复杂度的概念。在正式给出渐近时间复杂度以前,要先给出几个数学定义:
定义一:Θ(g(n))={f(n) | 若是存在正常数c一、c2和正整数n0,使得当n>=n0时,0<c1g(n)<=f(n)<=c2g(n)恒成立}html
定义二:O(g(n))={f(n) | 对任意正常数c,存在正整数n0,使得当n>=n0时,0<=f(n)<=cg(n)恒成立}
定义三:Ω(g(n))={f(n) | 若是存在正常数c和正整数n0,使得当n>=n0时,0<=cg(n)<=f(n)恒成立}
能够看到,三个定义其实都定义了一个函数集合,只不过集合中的函数须要知足的条件不一样。有了以上定义,就能够定义渐近时间复杂度了。
不过这里还有个问题:F(n)不是肯定的,他是在一个范围内变更的,那么咱们关心哪一个F(n)呢?通常咱们在分析算法时,使用最坏状况下的F(n)来评价算法效率,缘由有以下两点:
1.若是知道了最坏状况,咱们就能够保证算法在任什么时候候都不能比这个状况更坏了。
2.不少时候,算法运行发生最坏状况的几率仍是很大的,如查找问题中待查元素不存在的状况。且在不少时候,平均状况的渐近时间复杂度和最坏状况的渐近时间复杂度是一个量级的。
因而给出以下定义:设F(n)为算法A在最坏状况下F(n),则若是F(n)属于Θ(g(n)),则说算法A的渐近时间复杂度为g(n),且g(n)为F(n)的渐近确界。
仍是以上面的例子为例,则在上面定义中F(n) = (t1+t2)*n+t3。则F(n)的渐近确界为n,其证实以下:
证实:
设c1=t1+t2,c2=t1+t2+t3,n0=2
又由于 t1,t2,t3均大于0
则,当n>n0时,0<c1n<=F(n)<=c2n 即 0<(t1+t2)*n<=(t1+t2)*n+t3<=(t1+t2+t3)*n恒成立。
因此 F(n)属于Θ(n)
因此 n是F(n)的渐近确界
证毕
在实际应用中,咱们通常都是使用渐近时间复杂度代替实际时间复杂度来进行算法效率分析。通常认为,一个渐近复杂度为n的算法要优于渐近复杂度为n^2的算法。注意,这并非说渐近复杂度为n的算法在任何状况下都必定更高效,而是说在输入规模足够大后(大于临界条件n0),则前一个算法的最坏状况老是好于后一个算法的最坏状况。事实证实,在实践中这种分析是合理且有效的。
相似的,还能够给出算法时间复杂度的上确界和下确界 :
设F(n)为算法A在最坏状况下F(n),则若是F(n)属于Ο(g(n)),则说算法A的渐近时间复杂度上限为g(n),且g(n)为F(n)的渐近上确界。
设F(n)为算法A在最坏状况下F(n),则若是F(n)属于Ω(g(n)),则说算法A的渐近时间复杂度下限为g(n),且g(n)为F(n)的渐近下确界。
这里必定要注意,因为咱们是以F(n)最坏状况分析的,因此,咱们能够100%保证在输入规模超过临界条件n0时,算法的运行时间必定不会高于渐近上确界,可是并不能100%保证算法运行时间不会低于渐近下确界,而只能100%保证算法的最坏运行时间不会低于渐近下确界。
总结
算法时间复杂度分析是一个很重要的问题,任何一个程序员都应该熟练掌握其概念和基本方法,并且要善于从数学层面上探寻其本质,才能准确理解其内涵。在以上分析中,咱们只讨论了“紧确界”,其实在实际中渐近确界还分为“紧确界”和“非紧确界”,有兴趣的朋友能够查阅相关资料。
好了,本文就到这里了,但愿本文内容能对各位有所帮助。程序员
转载地址:http://www.cnblogs.com/leoo2sk/archive/2008/11/14/1332381.html算法