在第二章中,作者介绍了程序性能相关知识,主要介绍了程序性能的研究方法和程序性能分析的应用分析。首先介绍了程序的空间复杂性和时间复杂性的概念和分析意义,从这里我们可以理解程序空间复杂性和时间复杂性分析的必要性,在这里作者提出了程序分析员,这个角色通常是在程序员中产生或者程序员就兼职了该角色。既然分析有必要,那么程序空间复杂性和时间复杂性的分析就是程序员的职责之一,那么如何去做程序性能分析呢?接着作者就介绍了空间复杂性和时间负责性的分析方法并举例来做分析。空间复杂性的组成包括指令空间、数据空间和环境栈空间。指令空间与编译器、编译器选项和具体的机器环境有关,数据空间与常量数据、全局数据有关,环境栈空间与函数调用、函数形参、局部变量、动态分配变量有关。为了分析程序空间复杂性,引入实例特征和问题规模,实例特征一般指输入,输出数量,而问题规模一般指要解决问题的大小度,如排序问题中排序元素的个数。指令空间一般独立于实例特征和问题规模,数据空间和环境栈空间一般独立于实例特征,与问题规模有关。递归栈空间依赖于递归函数形参、局部变量和递归深度。程序空间复杂性的组成又可以划为固定部分和可变部分。固定部分包括指令空间、非复合数据的数据空间、非递归栈空间。可变部分包括复合数据的数据空间、递归栈空间、动态分配空间,程序空间复杂性也可以表示为S (P) = c + Sp(如果读过《深入理解计算机系统》,那么关于程序空间复杂性的分析是很容易理解的。你也可能看到这里关于程序空间复杂性的分析有点错误)。接下来举例来说明程序空间复杂性的分析方法,例子有累加的递归和非递归的实现、阶乘的递归和非递归的实现、顺序搜素的实现、排列的实现(关于递归和非递归,很多问题可以使用递归和非递归的方式去解决,递归方法在解决问题时简单、清晰、符合分析逻辑,但是空间复杂性高,性能较差,非递归方法在解决问题时需要较复杂的分析和实现,但是程序性能较高。关于这里的顺序搜索,实现方法并不是最好的,由于在这里仅仅分析空间复杂性)。
以下是排列问题的分析、实现和空间复杂性的分析。
排列问题分析:
排列集合为E,集合中元素为ei
对于n=1,排列集合中只有一个元素,即E = {e},只可能产生一种排列方式,所以perm(E)=(e)。
对于n>1情况,排列集合中有多个元素,即E = {e1,e2,e3,,,en},perm(E)= e1*perm(E1) + e2*perm(E2) + e3*perm(E3) + … + en*perm(En),其中perm(Ei)为集合E中移除元素ei后其余元素的排列。
这个排列定义是一个递归定义,也是一个数学归纳法的定义,可以用数学归纳法进行证明其正确性。一个完整的递归定义所需的基本部分和递归部分都已完成,可以使用递归法在程序中实现。
(一些问题,由于我们不能正确的运用递归方法来分析问题,最终导致我们无法采用递归方法来简单的解决问题,如排列问题、汉洛塔问题。)
(在递归问题上,分析是很重要的,递归问题可以分基本部分和递归部分来分析,可以采用数学归纳法来进行推理,得到基本表达式和递归表达式,分别得到递归的退出条件和递归调用实现)
排列问题实现:
//=================================================================================
//generate the permutation of a...
//a - permutation array
//k - begin point (if be called by other function , 0 should be)
//m - the max array index , n - 1 should be (n - array size)
//comments :
//=================================================================================
template<class T>
void Permutation(T a[],int k,int m)
{
if(k == m)
{
for(int i = 0;i <= k;i ++)
std::cout<<a[i]<<" ";
std::cout<<std::endl;
}
else
{
for(int i = k;i <= m;i ++)
{
Swap(a[i],a[k]);
Permutation(a,k + 1,m);
Swap(a[i],a[k]);
}
}
}
排列问题空间复杂性:
在这里有两个问题,环境栈的深度和递归函数调用次数,其中n为排列集合元素个数。
分析环境栈深度,在进行一个排列输出时,环境栈的深度值最大,可以得出环境栈最大深度为n。
分析递归函数调用次数,F(n)为排列集合为n时递归函数调用次数,根据排列的递归定义可得到函数调用次数的递归表达式:F(n) = n * F(n - 1) + 1
n = 1,F(n) = 1
n = 2,F(n) = 3
n = 3,F(n) = 10,依据递归表达式,可以计算函数调用次数。
(递归函数调用次数的分析采用了数学归纳法)
空间复杂性只与环境栈深度有关,环境栈深度为n,每次调用环境栈大小为:
返回地址+形参+局部变量,由此可以得出空间复杂性。
时间复杂性分析
影响程序空间复杂性的因素都会影响程序时间复杂性,但这些因素不是用来分析程序时间复杂性的好方法。
两种可行的分析程序时间复杂性的方法:1)操作计数2)执行步数
操作计数:首先选择一种或者多种操作,然后确定这些操作执行了多少次。
程序示例分析:
1) 求最大元素
2) 多项式求值(horner法则)
3) 计算名次
4) 名次排序
5) 选择排序
6) 冒泡排序
7) 插入排序
8) 顺序搜索
(这里的程序示例是最基本的和常用的程序,程序员应该能熟练的写出这些程序并能准确的做出分析)