初探算法复杂性度量

原创 2016年08月29日 11:08:08

初探算法复杂性度量

说到算法复杂性的度量,我们最熟悉的就是时间复杂度和空间复杂度,这两者均属于算法的事前估计。算法复杂性度量对一个算法的可操作性和效率性具有不可忽视的作用,可以说,一个算法的优异性与算法复杂性的优异性密切相关。


空间复杂度度量

空间复杂度(space complexity)是指问题的规模以某种单位从1增加到时,解决这个问题的算法在执行时所占用的存储空间也以某种单位由1增加到S(n),则称此算法的空间复杂度为S(n)。
   —— 殷人昆《数据结构(用面向对象方法与C++语言描述)》

我们先来谈谈空间复杂度,而以上是对空间复杂度的定义。要准确理解空间复杂度,我们从定义出发,先来理解问题规模和空进单位的概念。

问题规模

在一个问题的描述中,我们可以很容易地找到关于问题规模的描述。例如,在有n个学生资料中查找某一学生的资料,n就是问题的规模,又如对n阶线性方程进行求解,问题规模仍然是n。算法是针对实例而设定的,问题规模也就相应地代表了实例的特性。

空间单位

空间单位一般规定为一个工作单元所占用的存储空间大小,可以是一个简单变量,也可以是一个构造型变量。

然后我们再看看以下三组程序:

int abc(int a, int b, int c) {
    return a + b + c + a * b * c + a / b;
}
int sum(int a[], const int n) {
    int s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    return s;
}
int resum(int a[], const int n) {
    if (n <= 0) return 0;
    else return resum(a, n - 1) + a[n - 1];
}

观察以上三组程序,我们来思考一下它们对存储空间的使用。

这些程序所需的存储空间包含两个部分:

1)固定部分 这部分空间大小与输入输出个数多少、数值大小无关。主要包括存放程序指令代码的空间、常数、简单变量、定长成分(如数组元素、结构成分、对象的数据成员等)变量所占的空间等。这部分属于静态空间,只需要做简单的统计即可估算。

2)可变部分 这部分空间主要包括其与问题规模有关的变量所占空间、递归工作栈所用空间,以及在算法运行规程中通过new和delete命令动态使用的空间。

假设空间大小仅与时间规模n有关,可以通过分析算法规格说明,找出所需空间大小与n的一个函数关系,从而得到所需空间大小。

对于第一个程序,问题规模由a,b,c各占有一个空间单位,这样该函数所需存储空间为一常数。

第二个程序的问题规模为n,在程序中用到了一个整数n存放累加项个数,还用到一个整数s作为存放累加值的存储空间;另外对于数组a[]来说,只耗费了一个空间单元存放它第一个元素a[0]的地址。因此,此函数所需的存储空间也为一常数。

第三个程序使用了递归算法,问题规模也是n。在实现递归的过程中用到了一个递归工作栈,每递归一层就要加一个工作记录到递归工作栈中,工作记录为形式参数(a[]的首地址a[o]和n)、函数的返回值以及返回地址,保留了4个存储单元。由于算法的递归深度是n + 1,故所需的栈空间是4(n + 1)。

最不好估算的是涉及动态存储分配时的存储空间需求。若使用了k次new命令,动态分配了k次空间单元。如果没有使用delete命令释放已分配的空间,那么占用的存储空间数等于分配的空间数;如果使用了m次delete命令,就不能简单地拿new分配的空间数减delete释放的空间数,必须具体分析。

因此,分析一个算法所占用的存储空间要从各方面综合考虑。如对于递归算法来说,一般比较简短,算法本身占用的存储空间较少,但运行时需要附加一个堆栈,占用较多的临时工作单元。当写成非递归算法时,一般比较长,算法本身占用的存储空间多,但运行时需要较少的存储单元。

若一个算法为递归算法,其空间复杂度为递归所使用的递归工作栈空间的大小,它等于一次调用所分配的临时存储空间的大小乘以被调用的次数(即为递归调用的次数加1,这个1表示开始进行的一次非递归调用)。

算法的空间复杂度一般也以数量级的形式给出。如当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1);当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为O(log2n);当一个算法的空间复杂度与n成线性比例关系时,可表示为O(n);若形参为数组,则只需要为它分配一个存储由实参传送来的一个地址指针的空间,即一个机器字长空间;若形参为引用方式,则也只需要为其分配存储一个地址的空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。

时间复杂度度量

同样,我们先看看时间复杂度的定义。

时间复杂度(time complexity)是指当问题的规模以某种单位从1增加到n时,解决这个问题的算法在执行时所耗费的时间也以某种单位由1增加到T(n),则称此算法为T(n)。
—— 殷人昆《数据结构(用面向对象方法与C++语言描述)》

空间复杂度有空间单位来衡量,时间复杂度自然有时间单位来衡量。我们先来引入时间单位的概念。

时间单位

时间单位一般规定为一个程序步(program step),不同的语句有不同的程序步。

程序步

程序步是指在语法上或语义上有意义的一段指令序列,而且这段指令序列的执行时间与实例特性无关。

为了确定算法中每一个语句的程序步数,我将一一分析各种语句的程序步数。

1) 注释 程序步数为0,因为它是非执行语句。

2) 声明语句 程序步数为0。声明语句包括定义常数和变量的语句,用户自定义数据类型的语句,确定访问权限的语句,指明函数特征的语句。

3) 表达式 如果表达式中不包含函数调用,则程序步数为1.如果表达式中包含函数调用,还需要加入分配给函数调用的语句。

4) 赋值语句 <变量>=<表达式>的程序步数与表达式的程序步数相同。但如果赋值语句中的变量是数组或字符串(字符数组),则赋值语句程序步数等于变量的体积加上表达式的程序步数。

5) 循环语句
 1、while <表达式> do …….
 2、do …… while <表达式>
 控制部分一次执行的程序步数等于<表达式>的程序步数。
 3、for( <初始化语句>;<表达式1>;<表达式2>) ……
 <初始化语句>、<表达式1>、<表达式2>可能是实例特性 (例如n)的函数,控制部分第一次执行的程序步数等于<初始化语句>与<表达式>的程序步数之和,后续执行的程序步数等于<表达式1>与<表达式2>的程序步数之和。

6) switch 语句 其中,首部switch(<表达式>)的程序步数等于表达式具有的程序步数;执行一个条件的程序步数等于它自己的程序步数加上它前面所有条件计算的程序步数。

7) if_then 语句
 if (<表达式>) <语句1>;
 else <语句2>;
分别将<表达式>、<语句1>和<语句2>的程序步数分配给每一部分。需要注意的是如果else不出现,则这部分没有时间开销。

8) 函数执行语句/函数调用语句 函数调用语句的程序步数为0.其时间开销计入函数执行语句。函数执行语句的程序步数一般为1。但是,当函数执行语句中包含有传值参数且传值参数的体积与实例特性有关时,执行函数调用的程序步数等于这些值参的体积之和。如果函数是递归调用,那么我们还要考虑在函数中的局部变量。如果局部变量的体积依赖于实例特性,需要把这个体积加到程序步数中。

9) 动态存储管理语句 这类语句有new object、delete object 和sizeof (object)。每一个语句的程序步数都是1。new 和 delete
还分别隐式地调用了对象的构造函数和析构函数,这时可以用类似于分析函数调用语句的方式计算其程序步数。

10) 转移语句 这类语句包括continue、break 、goto、return 和 return<表达式>。它们的程序步数一般都为1。但是在return<表达式>情形,如果<表达式>的程序步数时实例特性的函数,则其程序步数为<表达式>的程序步数。

利用上述语句的程序步数可以确定一个程序的程序步数。通常有两种确定程序步数的方法。

第一种方法是在程序中插入一个计数变量count,它是一个初始值为0的全局变量。

以一组程序为例:

int sum(int a[], const int n) {
    int s = 0;
    count++;
    for (int i = 0; i < n; i++) {
         count += 2;
         s += a[i];
         count++;
     }
     count += 2;
     count++;
     return s;
}

假设count的初始值为0,则程序执行结束后,在count得到程序总程序步数是3 * n + 4。

如果程序采用递归呢?采用count同样可以得到相应的程序步数。

float rsum(float a[], const int n) {
     count++;
     if (n <= 0) {
         count++;
         return 0;
     } else {
         count += 2;
         return rsum(a, n + 1)+a[n - 1];
     }
}

若设count的初始值为0,且设Trsum(n)是程序执行结束后的count值,从上面程序可以看到,当n = 0时,Trsum(0) = 2。当n > 0时,进入rsum(a, n)执行后先在count中累加2,再加上递归调用rsum(a, n - 1)累加1,以及之后计算出的Trsum(n - 1)的值。这样我们可以得到一个计算递归程序rsum(a, n)的程序步数Trsum(n)的公式:
   当n = 0时,Trsum(n) = 2;
   当n > 0时,Trsum(n) = 3 + Trsum(n - 1)

然后通过重复代入Trsum递归计算Trsum:
Trsum(n) = 3 + Trsum(n - 1)
= 3 + 3 + Trsum(n - 2) = 3 * 2 + Trsum(n - 2)
= 3 * 3 + Trsum(n - 3)
= ……
= 3 * n + Trsum(0) = 3 * n + 2

这样一比较迭代求和与递归求和的程序步数,发现后者的程序步数要少一些,但是这并不能说明后者比前者运行时间短。事实上,后者涉及递归调用语句,其程序步数的时间开销要大得多。故后者实际运行时间比前者要多。

确定程序步数的第二种方法时建立一个表,列出程序内各个语句的程序步数。但本人就在这里就不详细说明了。

最后注意一点:

一个语句本身的程序步数可能不等于该语句一个执行所具有的程序步数!!!

你也许会奇怪为什么时间复杂度不是什么O(n)吗,为什么我还讲这么多程序步数?其实程序步数对我们阅读代码判断时间复杂度很有帮助,后续我将详细地介绍渐进的空间复杂度和时间复杂度,敬请期待!

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

【数据结构与算法】复杂度度量与分析1.时间复杂度对算法的计算成本涵盖多个方面,为了确定计算成本的度量标准,我们常常从计算速度这一主要因素入手,如何度量一个算法所需的计算时间?随着输入规模的扩大,算法的...
  • sinat_26908721
  • sinat_26908721
  • 2016年07月26日 17:35
  • 774

关于McCabe环路复杂度计算

软考软件设计师中McCabe环路复杂度计算 环路复杂度用来定量度量程序的逻辑复杂度。以McCabe方法来表示。 在程序控制流程图中,节点是程序中代码的最小单元,边代表节点间的程序流。一个有e条边和n个...
  • aerchi
  • aerchi
  • 2016年10月31日 16:47
  • 4130

软件复杂性分析

1,目的:更好地对软件开发过程进行控制;                提高软件的可靠性和可维护性;                降低由复杂性引发软件错误的可能性;              ...
  • v_forget
  • v_forget
  • 2015年09月27日 18:53
  • 698

算法效率度量:时间复杂度和空间复杂度

算法效率的度量是通过时间复杂度和空间复杂度来描述的。 .时间复杂度 一个语句的频度是指该语句在算法中被重复执行的次数。算法中所有语句的频度之和记作T(n),它是该算法问题规模n的函数,时间复杂度...
  • katy_yuan
  • katy_yuan
  • 2016年08月29日 17:30
  • 570

算法复杂性和如何计算时间复杂度

一,定义 算法的复杂性有时间复杂性和空间复杂性之分 通常考虑3种情况下的时间复杂性:最坏,最好和平均情况下的计算复杂性;当然可操作性最好且最有实际价值的是最坏情况下的时间复杂性 T(n)=max(t(...
  • juanlansexuehua
  • juanlansexuehua
  • 2017年01月17日 17:16
  • 452

McCabe度量方法计算程序复杂度

软考软件设计师McCabe环路复杂度,09年的两个题,为什么不一样?—from 百度知道 为什么上半年的答案是8-7+2=3 弧数为8,节点为7,没问题; 下半年的却是9-7+2=4 下半年的弧为...
  • t_1007
  • t_1007
  • 2016年11月04日 14:32
  • 4654

计算机算法设计与分析

算法初识: ●算法就是一组有穷的 规则 ,它们规定了解决某一特定类型问题的一系列运算  。此外,算法还应具有以下五个重要特性: 确定性 , 有穷性 ,可行性 , 0个或多个输入 , 一个或多个输出。...
  • qq_15437629
  • qq_15437629
  • 2015年05月18日 20:57
  • 2616

算法度量方法——时间复杂度及空间复杂度

以前对这方面是一知半解,终于在一次大众点评的笔试中收到刺激。 步入正题:什么样的算法才是高效的算法?想必所有的人都这么想过:用最少的钱,花做最短的时间,买到最多的东西。同样,用最少的内存空间,花最短的...
  • dd864140130
  • dd864140130
  • 2014年11月29日 17:00
  • 2647

软件设计的复杂度

什么是软件设计的复杂度软件技术发展的使命之一就是控制复杂度(Complexity)。从高级语言的产生,到结构化编程,再到面向对象编程、组件化编程等等。本文介绍通过分解、改善依赖关系,以及抽象的方式来降...
  • HorkyChen
  • HorkyChen
  • 2015年04月30日 00:57
  • 4609

距离度量的表示法

1. 欧氏距离,最常见的两点之间或多点之间的距离表示法,又称之为欧几里得度量,它定义于欧几里得空间中,如点 x = (x1,...,xn) 和 y = (y1,...,yn) 之间的距离为: ...
  • hermito
  • hermito
  • 2014年11月10日 22:27
  • 3562
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:初探算法复杂性度量
举报原因:
原因补充:

(最多只允许输入30个字)