【数据结构与算法】复杂度度量与分析

来源:互联网 发布:软件测试计划的目的 编辑:程序博客网 时间:2024/06/06 18:55

【数据结构与算法】复杂度度量与分析

1.时间复杂度


对算法的计算成本涵盖多个方面,为了确定计算成本的度量标准,我们常常从计算速度这一主要因素入手,如何度量一个算法所需的计算时间?

随着输入规模的扩大,算法的执行时间可以表示为输入规模的一个函数,我们称作算法的时间复杂度,特定算法处理规模为n的问题所需的时间记作:T(n)

但是我们会注意到仅仅将处理规模n作为所时间函数的参数貌似不能确定时间,因为规模相同的输入通常都有多个,算法对其处理的时间所需的时间也不同。

    e.g  :n个元素组成的输入序列有n!中,有时所有元素都需要交换,有时不需要交换。

所以我们将T(n)进一步明确,做一次简化:在规模为n的所有输入中,选择执行时间最长的作为T(n),度量该算法的时间复杂度。

渐进复杂度

下面我们利用T(n)函数,对同一问题的两个算法A、B评价二者对于同一输入规模n的计算效率高低,然而我们可能会发现在较小规模与较大规模的n下,两者比较算法A处理较小的n同比稍逊于算法B,但是对于较大的n,A却表现的十分优越。

此时引出了一个问题,似乎简单对比T(n),不能就其性能优越做出总体性的评价。我们也容易注意到小规模问题所需的处理时间本来就相对较小,不同算法的实际效率差异就更不明显,所以这里做了个处理:忽略算法处理较小规模问题时的能力差异,转而去关注处理更大规模问题时的表现。

这种着眼长远、更为注重时间复杂度的总体变化趋势和增长速度的策略与方法,我们称之为:渐进分析(asymptotic analysis).

对于渐进分析,我们会首先关注T(n)的上界,引入一个“大O记号”定义为:

定义:存在正的常数c、函数f(n),使得对于n >> 2都有 T(n) <= c * f(n)。则认为n足够大的时候,f(n) 给出了T(n)增长速度的一个渐进上界,记为: T(n) = O(f(n)).性质:(1)对于任意正常数 c,有O(f(n)) = O (c * f(n))     (2)对于任意常数a > b > 0,有O(n^a + n^b) = O(n^a)意义:在大O记号下,(1)性质表示函数各项正的常系数可以忽略并等同于1.(2)性质表示:多项式的低次项均可忽略,只需保留最高次项。

2.空间复杂度

我们也可以引入的渐进记号对空间复杂度的度量,但我们更多地仅仅关注算法的时间复杂度,理由是就渐进复杂度的意义而言,在任一算法的任何一次运行过程中所消耗的存储空间,都不会多余其间所执行基本操作的累计次数。

更一般的,纵然每次基本操作所占用的存储空间是新开辟的,整个算法所需的空间总量,也不过于基本操作的次数的同阶。

注意除非特别申明,空间复杂度通常不计入原始输入所占用的空间。

3.复杂度分析

——常数O(1)

一般地,仅含一次或常数次基本操作的算法属于此类,此类算法通常不含循环、分之、子程序调用等。

此类算法是最为理想的,统称为:“常数时间复杂度算法”(const-time algorithm)

另外需要O(1)辅助空间的算法,称为就地算法(in-place algorithm).

——对数O(logn)

//问题:对于任意非负整数,统计其二进制展开中数位1的总数//输入:一个无符号数n//输出:n二进制展开数位1的总数int countOnes(unsinged int n){    int ones = 0;        //计数器复位    while(n > 0){        //n为0结束计数        ones  += (1 & n);//检查最低位,若1则计数        n >> 1;          //右移一位    }

算法复杂度分析:
1. n的二进制展开的总位数:1 + int(logn)
2. 根据右移性质:n缩减到0,至多右移 1 + int(logn) ,总的循环次数自然为1 + int(logn) 。
3. 分析算法,循环体之前后内只涉及常数次(逻辑判断、位与运算、假发、右移等)基本操作,因此countOnes()算法的执行时间主要有循环的次数决定,T(n)有:

O(1 + int(logn))  = O(int(logn)) = O(logn)) 

此类算法被称为“对数时间复杂度”(logarithmic-time algorithm)

一般地,凡运行时间复杂度为T(n) = O(log^c n)形式的这类算法均统称为“对数多项式时间复杂度的算法”(polylogarithmic-time algorithm)

——线性O(n)

凡运行时间可以表示和度量为T(n) = O(n)形式的这一类算法,均统称为:“线性时间复杂度算法”(linear-time algorithm)

若运行时间可以表示和度量为T(n) = O( f(n) ),而且f(x) 为多项式,则对应的算法称为“多项式时间复杂度算法”(polynomial-time algorithm),若一个问题存在一个复杂度在此范围以内的算法,则该问题是可有效求解的或易解的(tractable).

——指数O(2^n)

考虑算法1.2

//在禁止超过1位的移位运算的前提下,计算任意非负整数幂2^n//输入:正整数n//输出:2^n_int64 power2BF_I(int n){  //幂函数2^n 算法(蛮力迭代版),n >=0    _int64 pow = 1;        //累乘器初始化2    while(n-- > 0){        //迭代n轮        pow << = 1;        //将累乘器翻倍    return pow;            //返回    }}

复杂度分析
1. n轮迭代,整个算法共需要O(n)时间
2. r = 1 + int(logn) 作为输入规模,则运行时间为O(2^r)

–凡运行时间可以表示和度量为T(n) = O(a^n),均指“指数时间复杂度算法”。

从多项式到指数

前面说到,多项式时间复杂度算法的被视为可接受、可解地。通过对比可以发现多项式时间与指数时间复杂度之间在问题规模较大之后,两者的差距十分大,指数复杂度算法的实际效率急剧下降,计算时间之长很快就会令人难以忍受的地步,因此通常认为,指数复杂度算法无法真正应用于实际问题中,它们不是有效算法。相应地,不存在多项式复杂度算法的问题,称作难解的问题

4.输入规模

对于不同人在不同场合下关于“输入规模”的理解、定义、度量可能不尽相同,因此可能导致复杂度分析的结论有所差异,如在”countOnes()”算法得到复杂度为O(logn)的结论,若将输入规模表示为n的二进制展开,则复杂度为O(2^r)的结论。

对于计算问题的输入规模,应该严格定义为“用以描述输入所需的空间规模”。因此就上述例子而言,将输入参数n二进制展开的宽度r作为输入规模更为合理。对应地,以输入参数n本身的数值作为基准而得出的O(n)复杂度称为伪现行的复杂度

0 0
原创粉丝点击