您的位置:首页 > 其它

算法的时间复杂度

2012-10-08 22:58 197 查看
默认均为最坏的时间复杂度

时间复杂度包含 1:基本操作的源操作例如相乘  2:该基本操作的重复执行次数

相对的,空间复杂度是作为算法所需存储空间的度量 记作S(n)=O(f(n));

T(n)=O(f(n))表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度。

“O”的形式定义为:若f(n)是正整数n的一个函数,则x(n)=O(F(n))表示存在一个正的常数M,使得当n>=n0时,都满足|x(n)|<=Mf(n).

原型:extern void *realloc(void *mem_address, unsigned int newsize);   
语法:指针名=(数据类型*)realloc(要改变内存大小的指针名,新的大小)。//新的大小一定要大于原来的大小不然的话会导致数据丢失!   
头文件:#include <stdlib.h> 有些编译器需要#include <malloc.h>,在TC2.0中可以使用alloc.h头文件   

功能
:先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来       mem_address所指内存区域,同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。   

返回值
:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。    

注意:
这里原始内存中的数据还是保持不变的。当内存不再使用时,应使用free()函数将内存块释放。

定义:如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n),它是n的某一函数 T(n)称为这一算法的“时间复杂性”。

当输入量n逐渐加大时,时间复杂性的极限情形称为算法的“渐近时间复杂性”。

我们常用大O表示法表示时间复杂性,注意它是某一个算法的时间复杂性。大O表示只是说有上界,由定义如果f(n)=O(n),那显然成立f(n)=O(n^2),它给你一个上界,但并不是上确界,但人们在表示的时候一般都习惯表示前者。

此外,一个问题本身也有它的复杂性,如果某个算法的复杂性到达了这个问题复杂性的下界,那就称这样的算法是最佳算法。

“大 O记法”:在这种描述中使用的基本参数是 n,即问题实例的规模,把复杂性或运行时间表达为n的函数。这里的“O”表示量级 (order),比如说“二分检索是 O(logn)的”,也就是说它需要“通过logn量级的步骤去检索一个规模为n的数组”记法 O ( f(n) )表示当 n增大时,运行时间至多将以正比于 f(n)的速度增长。

这种渐进估计对算法的理论分析和大致比较是非常有价值的,但在实践中细节也可能造成差异。例如,一个低附加代价的O(n2)算法在n较小的情况下可能比一个高附加代价的 O(nlogn)算法运行得更快。当然,随着n足够大以后,具有较慢上升函数的算法必然工作得更快。

O(1)

Temp=i;i=j;j=temp;

以 上三条单个语句的频度均为1,该程序段的执行时间是一个与问题规模n无关的常数。算法的时间复杂度为常数阶,记作T(n)=O(1)。如果算法的执行时 间不随着问题规模n的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。

O(n^2)

2.1. 交换i和j的内容

sum=0; (一次)

for(i=1;i<=n;i++) (n次 )

for(j=1;j<=n;j++) (n^2次 )

sum++; (n^2次 )

解:T(n)=2n^2+n+1 =O(n^2)

2.2.

for (i=1;i<n;i++)

{

y=y+1; ①

for (j=0;j<=(2*n);j++)

x++; ②

}

解: 语句1的频度是n-1

语句2的频度是(n-1)*(2n+1)=2n^2-n-1

f(n)=2n^2-n-1+(n-1)=2n^2-2

该程序的时间复杂度T(n)=O(n^2).

O(n)

2.3.

a=0;

b=1; ①

for (i=1;i<=n;i++) ②

{

s=a+b;    ③

b=a;     ④

a=s;     ⑤

}

解: 语句1的频度:2,

语句2的频度: n,

语句3的频度: n-1,

语句4的频度:n-1,

语句5的频度:n-1,

T(n)=2+n+3(n-1)=4n-1=O(n).

O(log2n )

2.4.

i=1; ①

while (i<=n)

i=i*2; ②

解: 语句1的频度是1,

设语句2的频度是f(n), 则:2^f(n)<=n;f(n)<=log2n

取最大值f(n)= log2n,

T(n)=O(log2n )

O(n^3)

2.5.

for(i=0;i<n;i++)

{

for(j=0;j<i;j++)

{

for(k=0;k<j;k++)

x=x+2;

}

}

解: 当i=m, j=k的时候,内层循环的次数为k当i=m时, j 可以取 0,1,...,m-1 , 所以这里最内循环共进行了0+1+...+m-1=(m-1)m/2次所以,i从0取到n, 则循环共进行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6所以时间复杂度为O(n^3).

我们还应该区分 算法的最坏情况的行为和期望行为。如快速排序的最 坏情况运行时间是 O(n^2),但期望时间是 O(nlogn)。通过每次都仔细 地选择基准值,我们有可能把平方情况 (即O(n^2)情况)的概率减小到几乎等于 0。在实际中,精心实现的快速排序一般都能以 (O(nlogn)时间运行。

下面是一些常用的记法:

访问数组中的元素是常数时间操作,或说O(1)操作。一个算法 如 果能在每个步骤去掉一半数据元素,如二分检索,通常它就取 O(logn)时间。用strcmp比较两个具有n个字符的串需要O(n)时间 。常规的矩阵乘算法是O(n^3),因为算出每个元素都需要将n对 元素相乘并加到一起,所有元素的个数是n^2。

指数时间算法通常来源于需要 求出所有可能结果。例如,n个元 素的集合共有2n个子集,所以要求出所有子集的算法将是O(2n)的 。指数算法一般说来是太复杂了,除非n的值非常小,因为,在 这个问题中增加一个元素就导致运行时间加倍。不幸的是,确实有许多问题 (如著名 的“巡回售货员问题” ),到目前为止找到的算法都是指数的。如果我们真的遇到这种情况, 通常应该用寻找近似最佳结果的算法替代之。

 

转自:http://blogold.chinaunix.net/u/26481/showart_479537.html

 

算法时间复杂度分析基础
摘要
本文论述了在算法分析领域一个重要问题——时间复杂度分析的基础内容。本文将首先明确时间复杂度的意义,而后以形式化方式论述其在数学上的定义及相关推导。从而帮助大家从本质上认清这个概念。
前言
通常,对于一个给定的算法,我们要做两项分析。第一是从数学上证明算法的正确性,这一步主要用到形式化证明的方法及相关推理模式,如循环不变式、数学归纳法等。而在证明算法是正确的基础上,第二部就是分析算法的时间复杂度。算法的时间复杂度反映了程序执行时间随输入规模增长而增长的量级,在很大程度上能很好反映出算法的优劣与否。因此,作为程序员,掌握基本的算法时间复杂度分析方法是很有必要的。

但是很多朋友并不能清晰的理解这一概念,究其原因,主要是因为没有从数学层面上理解其本质,而是习惯于从直观理解。下面,我们就一步步走近算法时间复杂度的数学本质。

算法时间复杂度的数学意义

从数学上定义,给定算法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

}

}

我们来看看这个算法。其中t1、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) | 如果存在正常数c1、c2和正整数n0,使得当n>=n0时,0<c1g(n)<=f(n)<=c2g(n)恒成立}

定义二:Ο(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%保证算法的最坏运行时间不会低于渐近下确界。

总结

算 法时间复杂度分析是一个很重要的问题,任何一个程序员都应该熟练掌握其概念和基本方法,而且要善于从数学层面上探寻其本质,才能准确理解其内涵。在以上分 析中,我们只讨论了“紧确界”,其实在实际中渐近确界还分为“紧确界”和“非紧确界”,有兴趣的朋友可以查阅相关资料。

好了,本文就到这里了,希望本文内容能对各位有所帮助。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息