第1章 算法概述
1.1 什么是算法
我们先从人们日常生活中的几个事例谈起。*先看图1.1(a),这是学生一天中从早上起床到晚上就寝的活动过程:吃早饭、上课、吃午饭、午休、上课、吃晚饭、自习、睡觉,正常情况下每天这些活动按由上到下的顺序进行。然后,我们看图1.1(b),当学生要离开宿舍去上课时,针对天气是否下雨的情形需要做出如下决策:如果下雨,那么需要撑雨伞,否则(也就是不下雨),可以不带雨伞出门,其中,“撑雨伞”和“不撑雨伞”是一对互斥事件,只能发生其中一个事件。
回顾图1.1(a)表示的学生一天的活动,如果我们把时间尺度放大到一个月(假设30天)、一年(假设365天)、两年(假设730天)、三年(假设1095天)或四年(假设1460天),并且假定学生在假期每天的活动与非假期每天的活动相似。从宏观角度观察学生在一个月内的活动,如图1.1(c)所示,学生一个月的活动实际是对一天活动的30次重复。那么,我们可以很容易得出结论:学生一年重复一天的活动365次,两年重复一天的活动730次,三年重复一天的活动1095次,四年共重复一天的活动1460次。
图1.1 学生生活的日常实例
从辩证思维的观点深入分析上述学生的三个生活实例将会发现,学生的活动规律有且仅有上述三种结构:图1.1(a)表示活动具有顺序结构,图1.1(b)表示活动具有分支结构,图1.1(c)表示活动具有循环结构。事实上,任何事物的运动、发展和联系通常都可以用上述顺序、分支和循环结构来表示,而且这种表示的逻辑关系和层次结构十分清晰。对于复杂的事物运动规律,则可以用上述三种结构的组合(特别是嵌套)表示出来。尽管我们在实际生活和工作中面对的问题形形色色、各种各样,但是我们从辩证思维的观点深入分析,每一个复杂的问题或现象本质上都是上述三种结构的复杂组合,如果我们熟练掌握了上述三种结构的分析方法,就能够逐层“剥离”复杂问题或现象,使其结构简单而清晰地呈现在眼前,从而深入认识问题或现象的本质,为有效解决问题建立基础。
那么,上述生活实例与算法又有什么关系呢?我们从算法的观点看,上述三个活动都可以看作算法。通俗地说,算法就是解决问题的过程与步骤。事实上,早在8世纪,阿拉伯数学家、天文学家花拉子米(al-Khwarizmi)就用阿拉伯语写了一本教科书,如图1.2所示,系统介绍了+、.、×、÷、平方根和圆周率的计算规则,“算法”的雏形由此诞生,*初算法表示阿拉伯数字的十进制运算法则。
图1.2 花拉子米及所著教科书
几百年之后,当十进制计数法在欧洲被广泛使用时,“算法(algorithm)”这个单词被人们创造出来以纪念花拉子米先生。由此可见,算法概念诞生于9世纪,而现代计算机(准确地说是数字电子计算机)却诞生于1946年美国宾夕法尼亚大学,因此算法概念产生的时间比现代计算机要早得多。
我们来看如下算术四则运算的例子。
例1.1计算四则运算式的值:5+(7-1)÷3=?
众所周知,四则运算规则如下。
(1)先计算括号里面的式子,再计算括号外面的式子。
我们得到式子:5+(7-1)÷3=5+6÷3。
(2)先乘除,后加减。
我们得到式子:5+6÷3=5+2。
进而得到:5+2=7。
*终结果:7。
我们再看一个算术四则运算的例子。
例1.2计算四则运算式的值:5+(7-1)÷(3.3)=?
根据四则运算的规则,计算过程如下。
(1)先计算括号里面的式子,再计算括号外面的式子。
我们得到式子:5+(7-1)÷(3.3)=5+6÷0。
(2)先乘除,后加减。这时,我们发现6÷0不符合“除数不能为0”这一法则,因此我们说这个算式无解,也就是说*终结果为无解。
概括以上两个十分简单的算法实例,我们对广义算法和狭义的计算机算法的基本概念可以进行如下理解。
算法:指人或机器解决特定问题时,按照某种机械步骤一定可以得到问题结果的处理过程。当问题有解时,给出求解的结果;当问题无解时,给出无解的结论。
计算机算法:指计算机解决特定问题时的操作步骤,是计算机操作指令的有限序列。当问题有解时,给出求解的结果;当问题无解时,给出无解的结论。
可见,广义算法与计算机算法都是机械地解决问题的操作步骤,并且当算法执行结束时,一定会给出问题求解的结果。问题、算法与求解结果的关系用图1.3问题、算法与求解结果的关系图1.3表示。
值得注意的是,“无解结论”也是一种输出,算法一定要有输出。那么,我们很自然地会想到一个问题:计算机算法与早期的算法概念相同吗?早期的算法由人设计并由人作为算法步骤的执行者来完成计算任务,而计算机算算法也是由人设计但*终由计算机代替人作为算法步骤的执行者来完成计算任务。因此,我们说计算机算法的概念与早期的算法的概念没有本质差别,早期的算法是由人设计并执行以完成特定的计算任务,而计算机算法是在计算机系统上自动执行从而完成特定的问题求解。在本书后续章节中,我们所说的“算法”都是指计算机算法,为了方便表述,把“计算机算法”简称为“算法”。
在对算法概念有了上述理解之后,我们再分析算法具有哪些重要特征。一般来说,算法具有以下五个主要特征。
(1)有穷性:一个算法必须在执行有穷步之后结束,即构成算法的步骤是有限的,并且每一步都可以在有穷时间内完成,因此不能包含无限循环。
(2)确定性:算法中每一条指令必须有确切的含义,并且在给定问题输入的条件下算法只有唯一的一条执行路径,即对于相同的输入只能有相同的输出(这个特性对确定性算法成立,对概率算法等不确定性算法例外,有关详细分析请参考第3章)。
(3)可行性:算法中描述的操作都可以通过现有计算机的基本运算及其组合来实现。
(4)输入:算法有0个或多个输入数据,即算法可以没有输入数据,其所需数据可能包含在算法内部。
(5)输出:算法有1个或多个输出结果,即算法必须至少有一个处理结果,否则算法就毫无意义了,即使“无解”也是一种处理结果。
算法除了具有上述五个重要特征之外,还具有三大构成要素:操作(运算)、控制结构(规则)和数据结构,具体描述如下。
(1)操作。
算术运算:+,-,*,/,^。
关系运算:>,<,=,<=,>=,≠。
逻辑运算:and,or,not, 。
数据传送:输入、输出、赋值、过程调用等。
(2)控制结构:算法的控制结构给出了算法的框架,决定了操作步骤之间的逻辑次序,共有如图1.1所示的三种控制结构,即顺序结构、分支结构(也称选择结构)和循环结构。
(3)数据结构:算法操作的对象是数据,数据之间的逻辑关系、存储方式和处理方式就是数据结构。
1.2 为什么学习算法
需要掌握信息技术(如计算机科学与技术、软件工程、人工智能、数据科学与技术等)的人员不仅仅包含编程人员,还包括复合型人才,他们既能用数学方法严谨分析问题、求证问题和描述问题(数学建模),也能够以工程师的务实品格来解决问题,而系统训练思维和能力的*佳课程就是“算法”。
*先,算法思维已成为现代社会的基本思维方式。如果认为只有学习计算机的人才要了解算法,那就太片面了。算法其实是解决问题的步骤描述,这些步骤是解决问题的人通过思考和分析得出的。只有具备算法思维能力,才能设计相应的算法来解决不同领域中的复杂问题,而学习算法正是为了构建这种严谨思维和帮助做出*佳判断。在现代社会和未来高度智能化的社会中,算法无处不在,算法思维已经成为现代社会解决各种问题的基本工具和思维方式,算法思维训练能够使我们的思维变得更清晰、更有逻辑,而且学习算法不仅能使自己面对复杂事件时思维分析能力更强,也能够在大数据和人工智能时代为自己打下坚实的理论和技术基础,成为智能时代的高端人才和复合型人才,而不是一直停留在低水平的编程层次。
其次,算法设计与分析方法是计算机工作者需要掌握的**技能。对于一个给定的算法,我们通常需要做两项分析工作:一是从数学上证明算法的正确性,主要用到形式化证明的方法及相关推理模式,如循环不变式、数学归纳法等;二是在证明了算法正确性的基础上,分析算法的时间复杂度和空间复杂度,也就是算法的时空效率分析。算法的时空复杂度在很大程度上能很好地反映算法性能的优劣。因此,作为计算机工作者(如程序员、算法工程师、软件工程师等),掌握基本的算法设计与分析方法对设计高质量的算法和式。就像电灯、汽车一样,包括智能手计算机程序或软件是很有必要的。
*后,算法能够使我们从本质上理解以计算机为核心的现代网络化社会的运行模式中的安全风险和面临的严峻挑战,从而在现代社会中不盲从、不惶恐,更加主动地适应信息技术不断升级带来的社会和生活巨变。
总之,生活在以计算机为主流的现代社会,一个人如果不了解算法和算法思维方法,就很难在即将到来的智能社会中生活和工作得游刃有余。
1.3 如何表示算法
由于算法是计算机解决问题的一系列操作步骤,那么任何一个计算机可以执行的步骤序列从广义上说都可以看作一个算法。当然,构成算法的一系列操作步骤必须满足算法的基本特性。例如,图1.1所描述的操作步骤都可以看作一个算法,是用流程图描述的。例1.1和例1.2的四则运算是用自然语言描述的。除了可以用自然语言和流程图描述之外,算法还有许多其他描述方法,如盒图方法、问题分析图(problem analysis diagram, PAD)方法、伪代码方法、程序设计语言方法等,读者可以参阅程序设计语言相关的书籍。
本书使用自然语言、伪代码和程序设计语言(主要是C语言和C++语言)来描述和实现算法。我们使用上述三种方法的出发点是,虽然学习者容易理解自然语言描述的算法,但是与算法在计算机上的*终实现差别比较大,有些算法步骤之间复杂的逻辑关系用自然语言描述既冗长又往往存在歧义,而伪代码方法主要使用顺序、分支和循环三种逻辑关系描述结构,人们适当约定一些操作和运算,则能够比较简洁、清晰、逻辑严密地描述算法的关键步骤及其逻辑关系,特别是容易分析算法的正确性和时空复杂性,也比自然语言更接近计算机程序设计语言,它是介于自然语言和程序设计语言之间的一种算法描述方法,因此用伪代码描述的算法更容易变换为在计算机上能够执行的具体的计算机程序。然而,伪代码终究不是人们普遍熟悉和习惯的语言,看上去还是有一些专业性和陌生的感觉。如果使用程序设计语言描述算法,学习者能够比较容易地体验算法在计算机上*终执行时的形式,即算法对应的计算机程序。但是,用程序设计语言描述算法一般比较冗长,需要描述许多辅助操作细节,容易陷入程序设计语言的细节泥潭中,学习者必须熟悉所使用的程序设计语言才能正确理解算法。那么为什么还要用程序设计语言来描述算法呢?我们认为绝大多数学习者学习算法的*终目的是设计高质量的算法,并将自己所设计的算法转换为计算机可执行的程序,为用户提供服务。从这个意义上说,在学习和理解算法的过程中熟悉和体验算法的实现是一件快乐的事情。另外,把所学习的算法知识运用于具体实践与创新的过程,并将创新的成果服务于社会则具有更加重要的意义。
下面我们针对一个具体问题,*先给出用自然语言描述的算法,然后给出用伪代码描述的算法,*后给出程序设计语言描述的程序代码,供学习者体验如何描述算法。
例1.3狱吏问题。
【问题定义】谁能从监狱获得自由?古代有个国王要对囚犯进行大赦,让一个狱吏依次通过一排共n间锁着的牢房,并规定:每通过一次,按规则转动某些牢房中的门锁,每转一次,原来锁着的门被打开,而原来开着的门被锁上;通过n次后,门锁开着的牢房中的犯人获得释放。
温馨提示:请使用泸西县图书馆的读者帐号和密码进行登录