目录
“Programs=Algorithm+Data Structures”
引言
“Programs=Algorithm+Data Structures”
程序 = 数据结构 + 算法
瑞士计算机科学家尼古拉斯·沃斯在1984年提出这一著名论断,奠定了程序的基础概念。
由此可见,算法与数据结构有着千丝万缕的联系。
下面我们就来讨论一下算法和算法分析的相关知识。
什么是算法?
算法的定义
算法是对特定问题求解步骤的一种描述,他是指令的有限序列,其中每一条指令表示一个或多个操作。
算法具有的特性
有穷性
一个算法必须总是在执行有穷步之后结束,且每一步都可在有穷时间内完成。
确定性
算法中每一条指令必须有确切的含义,读者理解是不会产生二义性。
并且,在任何条件下,算法只有惟一的一条执行路径,即对于相同的输入只能得出相同的输出。
可行性
一个算法是能行的,即算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现的。
输入
一个算法有零个或多个的输入,这些输入取自于某个特定的对象集合。
输出
一个算法有一个或多个的输出,这些输出是同输入有着某些特定关系的量。
算法设计要求
正确性
算法应当满足具体问题的需求。
“正确”通常有以下层次:
- 程序不含语法错误
- 程序对于几组输入数据能够得出满足规格说明要求的结果
- 程序对于精心选择的典型、苛刻而带有刁难性的几组输入数据能够得出满足规格说明要求的结果
- 程序对于一切合法的输入数据都能产生满足规格说明要求的结果
显然第四层意义的正确性是极难实现的,故通常将第三层意义的正确性作为衡量一个程序是否合格的标准。
可读性
算法主要是为了人的阅读与交流,其次才是机器执行。
可读性好有助于人对算法的理解;晦涩难懂的程序易于隐藏较多错误,难以调试和修改。
健壮性
当输入的数据非法时,算法也能适当地做出反应或者进行处理,而不会产生莫名其妙的错误。
并且,处理出错的方法应是返回一个表示错误或错误性质的值,而不是打印错误信息或异常,并终止程序的执行,以便在更高的抽象层次上进行处理。
效率与低存储量需求
通俗地说,效率指的是算法执行的时间。
对于同一个问题如果有多个算法可以解决,执行时间短的算法效率高。
储存量需求只算法执行过程中所需要的最大储存空间。
效率与低存储量需求这两者都与问题的规模有关。
下面展开讨论效率与存储空间需求。
算法分析
算法效率的度量
度量方法
1.事后统计法
将算法实现,测算其时间和空间开销。
2.事前分析法
假设每条语句执行所需的时间为单位时间,将算法的运行时间的讨论转化为算法中所有语句的执行次数。公式如下:
算法运行时间 = (语句执行次数 × 该语句执行所需时间)
举个栗子,给二维数组赋值:
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
a[i][j] = 1;
第一条for语句执行 n+1 次(判断不符合循环条件,运行次数加一)。
第二条for语句执行 n*(n+1)次。
赋值语句执行 n*n 次。
则上述算法的时间消耗为(求和):
T(n) = 2n² + 2n + 1
时间复杂度
继续使用上面的栗子:
当时,T(n) / n² = 2,这表示T(n)与n²同阶,引入“O”表示,则有:
T(n) = O(n²)
T(n) = O(f(n))表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称做算法的渐进时间复杂度,简称时间复杂度。
时间复杂度还可分为三种:
- 最坏时间复杂度:最坏情况下,算法的时间复杂度
- 平均时间复杂度:所有可能输入实例在等概率出现的情况下,算法的期望运行时间。
- 最好时间复杂度:最好情况下,算法的时间复杂度
对于复杂算法,通常难以看出语句的执行次数,此时我们可以将算法分解成几个容易估算的部分,并利用下面的法则计算算法的时间复杂度:
- 加法法则:T(n) = T1(n) + T2(n) = O( f(n) ) + O( g(n) ) = O(max( f(n) , g(n) ))
- 乘法法则: T(n) = T1(n) * T2(n) = O( f(n) ) * O( g(n) ) = O( f (n) * g(n) )
频度
频度是指语句重复执行的次数。
举个例子:
{++x; s = 0; }
for(i = 1; i < n; ++i){ ++x; s += x; }
for(j = 1; j <= n; ++j)
for(k = 1; k <= n; ++k){++x; s += x; }
上述三段代码中,“++x”的频度分别为1,n和n²,这三个程序段的时间复杂度分别为O(1),O(n)和O(n²),分别称为常数阶,线性阶和平方阶。
除此之外还有对数阶,指数阶等,当n很大时,不同时间复杂度的算法执行所需时间非常悬殊:
算法的储存空间需求
空间复杂度
类似时间复杂度,空间复杂度作为算法所需储存空间的度量:
S(n)= O(f(n))
算法占据的空间
首先算法本身需要空间,包括指令、常数、变量和输入数据等。
其次有的算法实现额外空间,称为辅助空间。
举个例子,将一个数组的数字逆序
算法一:
for(int i = 0; i < n/2; i++){
t = a[i];
a[i] = a[n-1-i];
a[n-1-i] = t;
}
利用t交换每个a[i]与a[n-1-i]得到逆序数组,此时所需的辅助空间为常量t。
算法二:
for(int i = 0; i < n/2; i++)
b[i] = a[n-1-i];
新建一个数组b[n],将原数组按要求赋值得到逆序数组,此时所需的辅助空间为数组b[n]。
结语
关于效率与存储空间
本文主要介绍了算法的基本特点,设计原则与算法基于效率和储存空间的分析。
最后我想再对最后的例子做一些分析说明,
为了达成同一个目的,
算法一中,辅助空间只有一个变量t,但每次循环要做三次赋值语句,节省空间但消耗更多时间。
算法二中,辅助空间是一个新数组,比一个变量t要占据更多空间,但每次循环只做一次赋值,节省时间但需要更多空间。
由此可见,效率与储存空间两者往往是不能同时满足的。
因此在设计算法时,往往需要根据实际需求综合考虑。
祝大家都能设计出称心如意的算法!