算法是指完成一个任务所需要的具体步骤和方法。也就是说给定初始状态或输入数据,经过计算机程序的有限次运算,能够得出所要求或期望的终止状态或输出数据。
编程界的“Pascal之父”Nicklaus Wirth有一句人尽皆知的名言:“算法+数据结构=程序”。(Algorithm+Data Structures=Programs),可见算法对程序的重要性。
本文从算法的基本定义出发,详细解读了算法的发展历程、主要特征、衡量指标和算法设计的基本方法,供大家学习参考。
1. 算法的基本定义
百科百科对算法的定义是:算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。
一句话概括一下,算法就是解决问题的操作步骤。
2. 算法的发展历程
在我国古代,算法被称为“演算法”,关于算法的起源最早可以追溯到我国古代公元前1世纪的《周髀算经》,是算经的十书之一,原名《周髀》,主要阐述古代中国的盖天说和四分历法。在唐朝的时候,此书被定为国子监算科的教材之一,并改名为《周髀算经》。《周髀算经》中记载了勾股定理、开平方问题、等差级数等问题,其中用到了相当复杂的分数算法和开平方算法等。在随后的发展中,出现了割圆术、秦九昭算法和剩余定理等一些经典算法。
在西方,算法(algorithm)一词最早来源于9世纪波斯数学家花拉子米(花拉子米是代数与算术的创立人,被誉为“代数之父”,所著《代数学》一书,最早给出了一次和二次方程的一般解法),花拉子米的拉丁文译名是“Algoritmi”,英文对“算法”原译为“algorism”,意思是花拉子米的运算法则,指的是用阿拉伯数字进行算术运算的过程,在18世纪演变为“algorithm”。
阿拉伯数学家花拉子米
世界上第一个算法
公元前300年,“几何之父”欧几里得提出了人类史上第一个算法——欧几里得算法,又称辗转相除法,是求最大公约数的一种方法。它的具体做法是: 用较大数除以较小数,再用出现的余数(第余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。
辗转相除法举例:
求10,25的最大公约数:
25/10=2……5
10/5=2……0
所以10,25的最大公约数为5
辗转相除法代码实现:
int gcd(a, b)[ return b == 0 ? a : gcd(b, a % b); }//当余数为0时,最大公约数就是a,否则继续往下递归
世界上第一个算法程序
"If I should see you,after long year.How should I greet, with tears, with silence. "这是英国著名诗人拜伦的诗句,而世界上第一位程序员阿达·洛芙莱斯(Ada Lovelace),就是这位著名诗人的女儿,她编写了世界上第一个算法程序。1842年,Ada为巴贝奇分析机编写求解伯努利方程的程序,写作了第一份“程序设计流程图”,被珍视为“第一位给计算机写程序的人”。因为查尔斯·巴贝奇(Charles Babbage)未能完成他的巴贝奇分析机,这个算法未能在巴贝奇分析机上执行。
这张写满数学算法的巨幅图表,被视为“第一个计算机程序”,由埃达·洛夫莱斯编写, 发表于 1843 年的一篇关于“分析机” 的文章中。埃达对此给出精准描述如下:“这张表显示了运算过程中,机器各部分的所有连续变化。”
虽然这个算法未能实现,但Ada对计算机科学的贡献毋庸置疑,1953年,阿达之前对查尔斯·巴贝奇的《分析机概论》所留下的笔记被重新公布,并被公认对现代计算机与软件工程造成了重大影响。
“软件之母”Ada Lovelace
图灵机
20世纪的英国数学家图灵提出了著名的图灵论题,并提出一种假想的计算机的抽象模型,这个模型被称为图灵机。
图灵机,又称图灵计算机,是指一个抽象的机器,即将人们使用纸笔进行数学运算的过程进行抽象,由一个虚拟的机器替代人类进行数学运算。
它有一条无限长的纸带,纸带分成了一个一个的小方格,每个方格有不同的颜色。有一个机器头在纸带上移来移去。机器头有一组内部状态,还有一些固定的程序。在每个时刻,机器头都要从当前纸带上读入一个方格信息,然后结合自己的内部状态查找程序表,根据程序输出信息到纸带方格上,并转换自己的内部状态,然后进行移动。
图灵机是可被视作任意解决有限数学逻辑过程的机器,可以用来模拟任何算法。图灵机的出现解决了算法定义的难题,图灵的思想对算法的发展起到了重要的作用。
3. 算法的重要特征
一个算法应该具有以下五个重要的特征:
·有穷性(Finiteness)
算法的有穷性是指算法必须能在执行有限个步骤之后终止;
·确切性(Definiteness)
算法的每一步骤必须有确切的定义;
·输入项(Input)
一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
·输出项(Output)
一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;
·可行性(Effectiveness)
算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成(也称之为有效性)。
4.算法的评定
算法的复杂性是算法效率的度量,在评价算法性能时,复杂性是一个重要的依据。算法的复杂性的程度与运行该算法所需要的计算机资源的多少有关,所需要的资源越多,表明该算法的复杂性越高:所需要的资源越少,表明该算法的复杂性越低。
计算机的资源,最重要的是运算所需的时间和存储程序和数据所需的空间资源,算法的复杂性有时间复杂性和空间复杂性之分。
评定一个算法的优劣可以从以下5个方面进行衡量:
1)时间复杂度
是对一个算法在运行过程中临时占用存储空间大小量度。
2)空间复杂度
程序运行时基本操作所执行的次数。
3)正确性
算法的正确性是评价一个算法优劣的最重要的标准。
4)可读性
算法的可读性是指一个算法可供人们阅读的容易程度。
5)鲁棒性
鲁棒性是指一个算法对不合理数据输入的反应能力和处理能力,也称为容错性。
5.算法设计和分析的基本方法:
1)递归和递推。递归和递推是学习算法设计的第一步。递归算法是把大问题分解成相对较小的问题的过程,而递推就是从小问题逐步推导出大问题的过程。无论递归还是递推,都应该有初始状态。
2)搜索、枚举及优化剪枝。搜索在所有算法中既是最简单也是最复杂的算法。说它简单,是因为算法本身并不复杂,实现容易:说它最复杂,是因为要对搜索的范围进行一定的控制,不然就会出现超时等问题。搜索技术主要包括广度优先搜索和深度优先搜索。当其余算法都无法对问题进行求解时,搜索或许是唯一可用的方法。搜索是对问题的解空间进行遍历的过程。有时问题解空间相当庞大,完全遍历解空间是不现实的,此时就必须充分发掘问题所包含的约束条件,在搜索过程中应用这些条件进行剪枝,从而减少搜索量。
3)分治法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题…直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)…
4)动态规划法。动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
5)贪心算法(亦作饕餮法)。就是一种在每一步选择中都采取在当前状态下最好/优的选择,从而希望导致结果是最好!优的算法。贪心法可以解决一些最优性问题,如:求图中的最小生成树、求哈夫曼编码…对于其他问题,贪心法般不能得到我们所要求的答案。一旦一个问题可以通过贪心法来解决,那么贪心法一般是解决这个问题的最好办法。由于贪心法的高效性以及其所求得的答案比较接近最优结果,贪心法也可以用作辅助算法或者直接解决一些要求结果不特别精确的问题。