谈谈算法
http://www.mydrs.org 8/9/2001 大榕树
为了照顾初学者,这里先套用一些老句子:) (为了让大家容易明白,很多表达都不大规范) 算法是在有限步骤内求解某一问题所使用的一组定义明确的规则。(其实就是解决问题题的过程) 在这个过程中,无论是形成解题思路还是编写程序,都是在实施某种算法。 前者是推理实现的算法,后者是操作实现的算法。 一个算法应该具有以下五个基本特征: 1.有穷性 一个算法必须保证执行有限步之后结束; Note:所以“操作系统”就不是一个算法。 2.确切性 算法的每一步骤必须有确切的定义; Note: 不能有“a=3或a=5”之类的。 3.输入 一个算法有零个或多个输入,以刻画运算对象的初始情况。 4.输出 一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的 Note:不要以为“人看得见的”才叫输出!子程序的返回值也算是子程序的“输出” 5.可行性 算法原则上能够精确地运行,而且人们用笔和纸做有限次运算后即可完成。 2 算法复杂度 算法的复杂性是算法效率的度量,是评价算法优劣的重要依据。 一个算法的复杂性的高低体现在运行该算法所需要的计算机资源的多少上面。 所需的资源越多,我们就说该算法的复杂性越高;反之,所需的资源越低,则该算法的复杂性越低。 计算机的资源,最重要的是时间和空间(即存储器)资源。因而,算法的复杂性有时间复杂性和空间复杂性之分。 不言而喻,对于任意给定的问题,设计出复杂性尽可能低的算法是我们在设计算法是追求的一个重要目标; 另一方面,当给定的问题已有多种算法时,选择其中复杂性最低者,是我们在选用算法适应遵循的一个重要准则。 Note:在竞赛中,还要考虑实现算法的复杂程度 因此,算法的复杂性分析对算法的设计或选用有着重要的指导意义和实用价值。 关于算法的复杂性,有两个问题要弄清楚: 1.怎样表达一个算法的复杂性; 2.怎样计算一个算法的复杂性。 这里我们就不研究理论了,只告诉大家该掌握什么。 1.算法渐进复杂度 这是渐进算法分析(asymptotic algorithm analysis)的研究成果。 许多因素会影响程序的运行时间,因此精确的分析不大可能,意义也不大。 我们用采用一定“规模(size)”的数据作为输入时程序执行的“基本操作(basic operation)”数来描述时间效率。 这里,我们感兴趣的不是具体的运行时间(好算吗),而是它随输入规模的变化。 不变的叫常数运行时间,例如 program stupid; var n:integer; begin readln(n); writeln('Your input is:',n); end. 呵呵,很傻的例子。再看一个。 program morestupid; var i,n:integer; begin readln(n); for i:=1 to n do if i=n then writeln('Your input is:',i); end. 如果把if i=n then writeln...叫做一个basic operation的话,本程序的运行时间和n成正比,记做O(n) 如果改成: program mosttupid; var i,j,n:integer; begin readln(n); for i:=1 to n do for j:=1 to n do if (i=n)and(j=n) then writeln('Your input is:',n); end. 显然,就记成O(n^2) (n^2是平方记号) 还有的是O(2^n),这里举个例子: program toostupid; procedure doit(k:integer); begin if k>0 then begin doit(k-1); doit(k-1); end; end; begin readln(n); doit(n); end. 可能不是很明显。但是我们知道doit(k)在k=0的时候运行常数时间(设为1),设k=m的时候时间为f[m],则显然有: f[m]=2*f[m-1](m>0) 解这个递推式有:f[m]=2^m 故算法的时间渐进复杂度为O(2^n) 前面的例子叫做“多项式时间(渐进复杂度)”,这个例子叫“指数时间” 其他的多项式时间的例子: O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) <... 其他指数时间的例子: O(2^n) < O(n!) < O(n^n) <... 注意,计算机中,如非注明,logn是以2为底的对数。因为log(a,n)=logn/log(2,a) (换底公式),而log(2,a)是常数,所以 在这里,底数是什么并没有关系。 上面的O(1),O(n)都是采用的大O记号(big-Oh notation),它描述了算法的运行时间的上限。其他记号这里我就不讲了。 计算渐进复杂度可以这样进行: 统计每条语句的运行次数,得到一个关于N的式子。 如果它是多项式,就只保留最高项,不要系数。 例如: program stillstupid; var t,i,j,k,n:integer; begin readln(n); for i:=1 to n do k:=0; for t:=1 to 10000 do for i:=1 to n do for j:=1 to n do k:=0; end. 是O(n^2) 其他的一些技巧,如前面提到的解递推方程,下次再补充。
3 常见算法和算法设计思想(未完成) 递推 - 前面已经提到了。当直接计算比较麻烦的时候可以尝试用递推方法。 递归 - 把问题简化的重要思想 贪心 - 看起来简单,但是要证明贪心法可用也不是很容易的事情。 分治 - 当尝试直接递归的做遇到困难的时候,可以考虑加入一些附加操作,形成分治法。其实很多题目都可以使用它。包括很多计算几何题目。 枚举 - 简单的思想 状态空间搜索 - 就是...这个... 动态规划 - 后面有专题讨论 构造法 - 如果手算问题的过程中受到很大启发,那么可以考虑使用构造法。 模拟法 - 思路简单,有时也很有效 修正与调整 - 一种很重要的思想,在网络流,欧拉回路等问题的算法中都有体现 数学方法 - 例如列方程组,不等式组等,可以使问题清晰化。 4 活用算法知识 1.算法的组合 2.经典算法的变形与推广 3.研究困难问题的特殊情况 4.探索新的算法 一些典型的思想: 1.转化:递归和分治(见递归和分治部分) 2.修正 3.构造(见《其他方法》部分) 4.标记:信息的充分利用 5.决策 6.数学方法(见《数学方法》部分)
|