程序员应该知道的10大基础算法
[日期:2016-04-07] | 来源:数学与计算机 作者:张 建中 | [字体:大 中 小] |
数学技术之算法概论篇(6)
⑤当今世界最受人们重视的十大经典算法
当今世界,已经被发现或创造的经典算法数不胜数。如果,一定要你投票选出你最看重的十大算法,你会作何选择?2011年,有人在Stack Exchange上发起了提问,向网友们征集当今世界最为经典的十大算法。众人在一大堆入围算法中进行投票,最终得出了呼声最高的以下十个算法。鉴于其中多数算法前面已在不同的地方分别介绍过,这里从略。不过希望网友明白一点,以下票选出来的十大算法,受票选领域所限,不等同于、也绝非就是当今世界最为经典的十大算法,如许多常用的数值型经典算法并不在列等。
第一名:Union-find 合并操作和查询算法;
第二名:Knuth-Morris-Pratt字符串匹配算法;
第三名:BFPRT 算法;
第四名: Quick sort 快速排序算法 ;
第五名:Floyd-Warshall all-pairs 最短路径算法;
第六名:Gentry’s Fully Homomorphic Encryption Scheme 绅士完全同态加密机制算法;
第七名:Depth First Search、Breadth First Search 深度、广度优先搜索算法;
第八名:Miller-Rabin 作的类似的试验测试算法;
第九名:Binary Search 二分查找算法;
第十名:Huffman coding 霍夫曼编码算法。
⑥程序员应知道的10大基础算法
这里总结出的《程序员应知道的10大基础算法》,包含排序、查找、搜索和分类等几类算法,当然是程序员应当知道的一些基础算法,但是不是“程序员应知道的10大基础算法”,可能是仁者见仁、智者见智,不同看法一定很多。因其数量极其有限,既不可能全面,更不可能完善,只能是仅供参考而已。
算法一:快速排序算法
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(nlogn)次比较。在最坏状况下则需要Ο(n 2 )次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。快速排序使用分治法策略来把一个串行分为两个子串行,再分别进行排序。
算法步骤:
1 从数列中挑出一个元素,称为 “基准”。
2 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面。在这个分区退出之后,该基准就处于数列的中间位置,称为分区操作。
3 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代中,它至少会把一个元素摆到它最后的位置上去。
算法二:堆排序算法
堆排序(Heap sort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序的平均时间复杂度为Ο(nlogn) 。
算法三:归并排序算法
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是分治法的一个非常典型的应用。
算法步骤:
1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4. 重复步骤3直到某一指针达到序列尾;
5. 将另一序列剩下的所有元素直接复制到合并序列尾。
算法四:二分查找算法
二分查找算法是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。折半搜索每次把搜索区域减少一半,时间复杂度为Ο(nlogn) 。
算法五:线性查找算法 BFPRT
BFPRT算法解决的问题十分经典,即从某n个元素的序列中选出第k大(或第k小)的元素,通过巧妙的分析,BFPRT可以保证在最坏情况下仍为线性时间复杂度。该算法的思想与快速排序思想相似,当然,为使得算法在最坏情况下,依然能达到o(n)的时间复杂度,算法作者对此做了精妙的处理。
算法六:深度优先搜索算法 DFS
深度优先搜索算法(Depth First Search),是搜索算法的一种。它沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。DFS属于盲目搜索。
深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。一般用堆数据结构来辅助实现DFS算法。
深度优先遍历图算法步骤:
1. 访问顶点v;
2. 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
3. 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。
算法七:广度优先搜索算法 BFS
广度优先搜索算法(Breadth First Search),是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树(图)的宽度遍历树(图)的节点。如果所有节点均被访问,则算法中止。BFS同样属于盲目搜索。一般用队列数据结构来辅助实现BFS算法。
算法步骤:
1. 首先将根节点放入队列中;
2. 从队列中取出第一个节点,并检验它是否为目标。如果找到目标,则结束搜寻并回传结果,否则将它所有尚未检验过的直接子节点加入队列中;
3. 若队列为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”;
4. 重复步骤2。
算法八:Dijkstra算法
戴克斯特拉算法(Dijkstra’s algorithm)是由荷兰计算机科学家艾兹赫尔·戴克斯特拉提出。迪科斯彻算法使用了广度优先搜索解决非负权有向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。
算法九:动态规划算法
动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的算法,通过把原问题分解为相对简单的子问题的方式求解一些复杂的问题。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。
动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化为存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。动态规划最经典的问题当属背包问题。
算法步骤:
1. 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
2. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
算法十:朴素贝叶斯分类算法
分类是将一个未知样本分到几个预先已知类别的过程。在众多的分类模型中,应用最为广泛的两种分类模型是决策树模型(Decision Tree Model)和朴素贝叶斯模型(Naïve Bayesian Model,NBC)。决策树模型通过构造树来解决分类问题。首先利用训练数据集来构造一棵决策树,一旦树建立起来,它就可为未知样本产生一个分类。在分类问题中使用决策树模型有很多的优点,决策树便于使用,而且高效;根据决策树可以很容易地构造出规则,而规则通常易于解释和理解;决策树可很好地扩展到大型数据库中,同时它的大小独立于数据库的大小;决策树模型的另外一大优点就是可以对有许多属性的数据集构造决策树。决策树模型也有一些缺点,比如处理缺失数据时的困难,过度拟合问题的出现,以及忽略数据集中属性之间的相关性等。
朴素贝叶斯分类算法是一种基于贝叶斯定理的简单概率分类算法。贝叶斯分类的基础是概率推理,就是在各种条件的存在不确定、仅知其出现概率的情况下,如何完成推理和决策任务。概率推理是与确定性推理相对应的。而朴素贝叶斯分类器是基于独立假设的,即假设样本每个特征与其他特征都不相关。
朴素贝叶斯分类器依靠精确的自然概率模型,在有监督学习的样本集中能获取得非常好的分类效果。在许多实际应用中,朴素贝叶斯模型参数估计使用最大似然估计方法,换言之朴素贝叶斯模型能工作并没有用到贝叶斯概率或者任何贝叶斯模型。尽管是带着这些朴素思想和过于简单化的假设,但朴素贝叶斯分类器在很多复杂的现实情形中仍能够取得相当好的效果。
数学杂谈:数学技术之算法概论篇
计算机算法是在计算机上有限步内求解某一问题所使用的一组定义明确的规则或对解题步骤的精确描述,通俗点说,就是计算机解题的过程,即以一步接一步的方式详细描述计算机如何将输入转化为所要求的输出的过程,下面简称其为算法。在这个过程中,无论是形成解题公式还是编写程序,都是实施某种算法,前者利用推理实现算法,后者通过计算机操作实现算法。
计算机算法的基本特征可概括为:
① 正确性:对于任意一组输入,包括合理的输入与不合理的输入,总能得到预期的输出;如果一个算法只是对合理的输入才能得到预期的输出,而在异常情况下却无法预料输出的结果,那它就是不正确的;
② 确切性:算法的每一步必须有确切的定义,无二义性;
③ 可行性:算法是由一系列具体步骤组成的,每一步都能够准确运行,有确定的执行顺序;
④ 有穷性:算法必须保证经有限步运行之后结束,即算法的步骤必须是有限的。在任何情况下,算法都不能陷入无限循环中;
⑤ 输 入:算法有一个或多个输入,表示运算对象的初始条件;
⑥ 输 出:算法有一个或多个输出,反映运算的最终结果。
描述算法的方法有多种,常用的有自然语言、结构化流程图、伪代码和PAD图(Problem Analysis Diagram)等,其中最普遍的是流程图。虽然算法与计算机程序密切相关,但二者还是不同的,计算机程序是算法的一个实例,是将算法通过某种计算机语言表达出来的具体形式;同一个算法可以用不同的计算机语言来表达。
对同一个计算问题,不同人会用选用不同的算法,而不同的算法使得计算机的运行效率、解题精度和对计算资源的需求会有一定的差异。在实际问题中遇到的高难度计算问题,有的问题在巨型计算机上用普通算法求解可能要用数天时间,甚至也难以找到可用的解,但用一个好的算法,即使在普通的微机上,只用几分钟就可以找到满意的解。因此,用计算机求解一个实际问题的计算速度和结果满意度不仅仅与计算机设备的水平有关,更取决于求解该问题算法水平的高低和对问题的适应性,由此可见算法的重要性。算法研究的重点随问题的不同而异,主要有算法设计和分析、计算复杂性和新的高效算法设计与研究等。
对一个给定问题的算法要进行设计和分析。算法设计,就是对一个给定问题设计出良好的算法,并研究设计算法的规律及其有关的方法;算法分析,就是对一个给定问题设计出来的算法,利用数学工具,研究该算法对问题的适应性和算法的稳定性、收敛性、复杂性和误差问题等。
评价算法优劣的标准有:
①时间复杂度:同样的问题规模需花费多少时间;
②空间复杂度:同样的问题规模需花费多少空间(主要是内存);
以上两点越小越好
③稳定性:不会因为输入稍有不同而导致计算结果不稳定的情况发生;
④算法思路是否简单:越简单越容易实现越好,最好有现成软件系统可用。
算法复杂度是对算法在计算机上运行时所需要的计算机资源的度量,需要的时间资源量(如计算所需的步数或反复执行指令的条数)称作时间复杂度,需要的空间资源量(即需占用存储空间的大小)称作空间复杂度,是对算法效率的度量和评价算法优劣的重要依据。这些量应该集中反映算法中所采用方法的效率,而从运行该算法的实际计算机中抽象出来。换句话说,这些量应只依赖于算法要解的问题的规模、算法的输入、输出和算法本身。
计算机和数学技术的快速发展,近些年来出现了许多新算法。它们技巧性强,在时间复杂度、空间复杂度和计算精度等方面各占一定优势,应用广泛,效果显著。
算法对我们真有那么重要吗?现在,这些看不见、摸不着的算法正在掌控着我们与数字世界的互动,从谷歌网站上推荐图书、电影和音乐的算法到Facebook网站上推荐朋友的算法,从操纵华尔街股票交易的算法再到各种搜索引擎的算法及好莱坞预测电影票房的算法,算法似乎已无声地渗入到我们的世界并重塑着我们身处的世界。有专家指出:计算机用来做决定的算法正在以“随风潜入夜,润物细无声”的方式,慢慢渗透进我们日常生活的方方面面。这些看不见摸不着的算法正在慢慢掌控着我们与电子世界的相互交流,现在是一个“算法为王”的时代。随着算法开始将其影响力延伸并塑造我们身处的世界,现在已经到了我们必须透彻地了解算法的时候了。
数学应用、应用数学,数学计算、计算数学,数学技术、技术数学,粗看起来,它们都和数学密切相连,都是以数学为工具,解决现实世界中遇到的各种各样实际中遇到的问题;细分起来,在发展过程、研究方向、考虑问题的重点和评价优劣的标准等方面确有不少差异。但不管怎么说,以数学为思维的方法,研究的工具,把实际问题经抽象处理,构建可用的数学模型,研究其中用到的各类算法,编制成在计算机上可运行的程序,通过计算机上的实际计算和计算结果的分析处理,解决遇到的实际问题,这些都是它们应含有的思想和实际应用中要处理的问题。
说到计算机,就像显微镜对医学、望远镜对天文学一样,是数学应用、数学计算、数学技术中不可缺少的设备,时时、处处都要用到计算机,有的还要用到巨型计算机。计算机使数学原理得以实现,为数学应用开辟了无限广阔的天地;计算机是具体化了的数学,现代数学的实验室,进行现代数学研究和实际应用时必不可少的工具。这里只能挂一漏万,介绍一下计算机上数据的表示、数学运算及其误差和几个简单的算例。
数据(Data)是信息的载体,是计算机加工处理的对象,描述客观事物的数、字符,以及所有能输入到计算机中并被计算机程序识别和处理的符号集合,它能够被计算机识别、存储和加工处理。它是计算机程序加工的原料,应用程序处理各种各样的数据。计算机里的数据可以是数值型数据,也可以是非数值型数据。数值型数据是一些整数、实数或复数,主要用于工程计算、科学计算和商务处理等;非数值型数据包括字符、文字、图表、图形、图像、语音等。数据元素(Data Element)是数据的基本单位。
计算机中,以位(0或1)表示数据。数据的最小的寻址单位称为字节(通常是八位),机器码指令处理的单位,称作字长。大部分对字长的指令解译,主要以二进制为主,如一个32位的字长,可以表示从0至232-1的无符号整数值,或者表示从-(232-1)至(232-1)有符号整数值。存在着特殊的算术指令,对字长中的位使用不同的解释,以此作为浮点数。
数据类型(Data type)是用来约束数据的解释,有很多种,最简单的就是数字。数据也可以是文字、图像、声音等,可用于科学研究、设计、查证等。在编程语言中,常见的数据类型包括原始类型(如:整数、浮点数或字符)、多元组、记录单元、代数数据类型、抽象数据类型、参考类型、类别以及函数型等。数据类型描述了数值的表示法、解释和结构,并以算法操作,或是物件在内存中的储存区,或者其它储存装置,随不同的计算机语言系统会有所不同。
数据在计算机里的基本运算和操作有如下四类:
1.算术运算:加减乘除等运算;
2.逻辑运算:或、且、非等运算;
3.关系运算:大于、小于、等于、不等于等运算;
4.数据传输:输入、输出、赋值等运算。
用计算机高级语言研制的程序,大都含有数值计算;因此,在计算机应用中,进行数值计算是其最重要的功能之一。计算机语言中的数值表示及其运算和数学中的数值与运算有所不同,对此要有明确的认识和严格的区分,并在计算机上实施实际计算中给予足够的重视和进行细致的分析。在计算机各种不同的编程语言中,数值计算多采用32位(4字节)的单字长或64位(8字节)的双字长作为一个数存单元,每一个数值都存放在计算机里数存的一个单元中。数值的大小和数值的精度都受到一定限制。
受计算机字长的限制,输入到计算机里的数会有原始误差和舍入误差,经计算得到结果的数含有运算误差、传递误差和累积误差,计算公式进过简化、离散、近似逼近等处理也会出现误差。在计算机亿万次计算过程中,误差的积累和传递是纸上手工计算中无法体会和了解的,两者有很大不同,甚至在数学上成立的恒等式,在计算机上实施计算的过程中也会产生异化而不再成立。
这里将对上述问题及应注意的一些事项进行一些简单扼要介绍。
从我们接受基础教育开始到中学学习结束,接触到的数多是数学中的数,像小学中的自然数、小数、分数,中学时的负数、无理数、实数、复数等等,如53、123.618、2/3、-35.4、√2、3+10i(这里,i= √-1,为虚数单位)等,用来表示一个明确的数;代数中,更是用英文字母表示非常广义的数,如向量、矩阵等。但在计算机上和计算机语言中表示的数和运算,和数学中的数和运算却有所不同,即计算机上计算公式里的运算必须是计算机上可实际执行的运算,参与运算的数是有限小数或整数,并有一定的表示格式。
在计算机内,所有的数均用二进制表示,优点在于表示容易、物理实现简单、节省设备、代数运算简单可靠、逻辑运算方便。在计算机里,除二进制数(用B表示)外、还有八进制数(用O表示)、十进制数(用D表示)、十六进制数(用H表示)和二/十进制数(全名为“二进制编码的十进制数”,用BCD表示)等。
计算机上的数,用二进制的一位数码表示数的符号,称为“数符”,且用“0”表示正数,“1”表示负数。小数点的位置隐含表示,以节省存储空间。隐含的小数点位置有固定和可变两种,分别称为定点数和浮点数。
1.定点数表示法
定点整数:最高二进制的一位数码表示数的符号,小数点位置约定在最低数值位的后面,用于表示整数。
定点小数:最高二进制的一位数码表示数的符号,小数点位置约定在符号位的后面,用于表示绝对值小于1的有限位小数。
2.浮点数表示法
阶符:位于左侧最高位二进制的一位数字,表示阶码的符号;
阶码:表示指数部分,阶码的位数决定数的范围;
数符:二进制的一位数字,表示数的符号;
尾数:小于1的小数,尾数的位数(长度)决定数的精度。
在计算机语言中,把数分为实型数和复型数两大类,分别和数学中的实数和复数相对应。在实型数中,又有整型数、定点数和浮点数之分,其中整型数相当于数学中的整数,定点数相当于数学中的小数,浮点数又称作指数记数法,相当于数学中的科学计数法,不同只在于表示方法,例如,数学中的-79*105,在计算机上用浮点数表示为-0.79E7,其中-0.79为其尾数部分,E7为代表107的指数部分。那么像√2 ,2/3等无理数、分数,在计算机及其语言中,又表示成什么样子呢?
在BASIC语言中,有一个计算平方根的函数SQR(X),和数学中的√X起同样作用,都是求X的平方根,如2的平方根SQR(2)=√2 ,只是SQR(X)被称作函数,受计算机字长的限制,不再是无理数,而是一个有限字长的浮点数,取为√2 的近似值;像2/3,在BASIC语言中,称作表达式,所得的是分子除以分母后所得的结果,只是为了计算机方便和清晰明了采用2/3这种表示方法。在计算机上使用的2/3,实际上是2除以3后的用有限字长表示的2/3的近似值。
此外,在计算机语言中,数的大小有着明确规定的范围。如在BASIC语言中,数的绝对值取值范围为[2.938736*10-39,1.701412*1038];绝对值低于2.938736*10-39,计算机判为0,称作下溢为0;超过上限1.701412*1038,计算机将显示出错信息,称为上溢出错停机;而在数学当中,数的大小是无限的,范围是(-∞,+∞)。
在数学中,0.1=1/10,是一个不存在误差的小数;但在计算机上,0.1用八进制将无法精确表示,是一个含有误差的数。
计算机中的复数,把实部和虚部分开存放,通过内部子程序实现复数各种不同类型的运算。