Java算法从菜鸟到达人
上QQ阅读APP看书,第一时间看更新

1.1 算法在计算机系统中的作用

算法是当代计算机系统中的核心内容,是否具有足够的算法知识与坚实的技术基础是区分熟练的程序员(达人)与初学者(菜鸟)的一个重要特征。下面将简单阐述什么是算法,以及为什么要学习和研究算法。

1.1.1 算法的定义

简单来说,算法(Algorithm)就是定义良好(没有公理性的矛盾,不会推出与实际情况相悖的情况)的计算过程,它取一个数或一组数据作为输入,通过一系列的计算步骤,产生出一个数或一组值作为输出。亦即,算法被用来描述一个特定的计算过程来实现某种输入/输出关系。

下面是排序问题的一个形式化定义:

输入:n个数构成的一个序列<a1,a2,…,an>。

输出:对输入序列的一个排列(重排),使得

可以把算法看作是一种工具,用来解决具有良好规格说明的计算问题。有关该问题的表述可以用任意语言或形式(包括自然语言、计算机程序、数学方程等)来说明所需的输入/输出关系,与之对应的算法则描述了一个可以实现这种输入/输出关系的计算过程。如果一个算法对其每一个输入实例,都可以输出正确的结果并按时停止,则称它是正确的。不正确的算法对于某些输入来说,可能无法停止,或者停止时给出的结果不符合预期。然而,在某些情况下,如果算法的错误率可以得到控制,那么它们也是有实际作用的,但是一般而言,我们主要关注与讨论正确的算法。

1.1.2 算法的地位

算法与计算机领域中的其他技术一样,也是一种技术,并且在飞速发展。那么相对于其他的技术,算法在当代计算机科学领域中是否真的那么重要?答案是“是的”。尽管对于有些应用来说,在应用这一层面上没有什么特别明显的算法方面的要求(例如,基础的Web应用),但计算机领域大多数问题对算法都有一定程度的要求,如硬件的设计需要用到算法,操作系统的设计需要用到大量的算法,任何GUI设计也要依赖于算法,此外,网络路由对算法也有很大的依赖,将高级语言代码编译为机器代码,需要编译器、解释器或汇编器来共同完成,这些过程都要用到大量的算法。可以说,算法是当代计算机中大部分技术的核心。那么,是否只要找到实现某一输入/输出关系的算法就足够了?显然不是,除非计算机运行速度无限快,所有计算过程中用到的资源(包括存储、传输等)无限多且免费,那么,对于一个问题来说,任何一个可以解决它的正确方法都是可以的。然而,现实情况是计算机还无法做到无限快,存储、传输等资源还无法免费。因此在时间、空间资源都有限的情况下,这些资源必须被尽可能有效的使用,那么设计在时间、空间上高效的算法将具有极大的实际意义。

针对同一个问题,很多时候存在很多候选解,这些不同解的效率往往相差很大,这种效率上的影响往往比硬件和软件方面的差距还要大,因此在算法层面寻找一个问题的最优解常常是一个很大的挑战。

例如,针对排序问题,后面的章节会介绍多种算法来完成排序,其中不同的算法在完成对指定规模数据排序时所需要的空间与时间资源会有很大不同。例如插入排序算法,对n个数据项进行排序的时间大约等于c1·n2,其中c1是一个不依赖于n的常量。另外一种算法——归并排序,为了排列n项数据,所花的大致时间是c2·nlgn(其中lgn是指log2nc2是不依赖于n的常数)。c1通常小于c2,然而归并排序的运行时间中的因子lgn小于插入排序算法中的因子n,因此就运行时间来说,当输入规模较小时,插入排序可能要比归并排序更快一些,当输入规模增大到一定程度的时候(n足够大),归并排序就会比插入排序运行得更快了,两种算法的差别就显现出来了。假设计算机A每秒可以执行1000万条指令,计算机B的计算速度是计算机A的100倍,现在需要对100万个数字进行排序,方案一为在计算机A上采用归并排序算法(所得到的代码需要50·nlgn条指令,c2=50),方案二为在计算机B上采用插入排序算法(所得到的代码需要2·n2条指令,c1=2)。

可以计算出,对100万个数字进行排序,方案一花费的时间为:

方案二花费的时间为:

可以看出,方案一中的计算机A虽然比计算机B计算能力差100倍,但是因为它采用了一个运行时间增长更缓慢(时间复杂度更低)的算法,完成同样的排序任务速度也比方案二快了20倍,当对1000万个数字进行排序时,这一差别将会更大。

1.1.3 一个简单的算法

首先来看一个简单的算法,然后以这个算法为例,让大家对算法有更为直观的感受。这里要分析的是前面提到过的插入排序算法,它解决的是排序问题:

输入:n个数构成的一个序列<a1,a2,…,an>。

输出:对输入序列的一个排列(重排),使得

待排序的数字被称为关键字(key)。

插入排序的工作原理与打牌时整理手中牌的思路相似。在开始摸牌时,手中是空的,接着每次从桌上摸起一张牌,并将它插入左手已经握着的牌中,并保证手中的牌在任何时刻都是排好序的,为了找到这张牌的正确位置,需要将摸到的牌与手中已有的牌依次从左到右进行比较。

因此用插入法解决排序问题通用的做法就是将第一个元素看作是有序的元素(即将待排序列的第一个元素看作是有序序列),然后将第二个元素和第一个元素进行比较,将该元素插入序列中合适的位置。然后再将第三个元素和当前有序序列(即整个待排序列的前两个元素)进行比较,用同样的方法将第三个元素插入到合适的位置,使得前三个元素有序。以此类推,直到所有的元素都有序为止。

下面给出插入排序算法的伪代码:

将完成插入排序算法的过程命名为INSERTION-SORT,它的参数是一个数组A,包含了n个待排序的数据。当过程INSERTION-SORT执行完毕后,输入的数组A中的元素已经排好顺序。下标j指示了当前待插入的数字,在外层for循环的每一轮迭代的开始,包含元素A[1,…,j-1]的子数组中的数据已经排好序,包含元素A[j+1,…,n]的子数组中的数据仍然为原始的顺序。图1-1展示了这个算法在数组A=<21,25,49,25*,16,8>上的工作过程。

图1-1 各趟排序后的结果