《数据结构与算法:Python语言描述》一第1章 绪论

本节书摘来自华章出版社《数据结构与算法:Python语言描述》一书中的第1章,第1.1节,作者 裘宗燕,更多章节内容可以访问云栖社区“华章计算机”公众号查看

第1章 绪论

作为基于Python语言的“数据结构与算法”教程,本章首先讨论一些与数据结构和算法有关的基础问题,还将特别关注Python语言的一些相关情况。

1.1计算机问题求解

使用计算机是为了解决实际问题。计算机具有通用性,其本身的功能很简单,就是能执行程序,按程序的指示完成一系列操作,得到某些结果,或者产生某些效果。要想用计算机处理一个具体问题,就需要有一个解决该问题的程序。经过长期努力,人们已经为各种计算机开发了许多有用的程序。在面对一个需要解决的问题时,如果恰好有一个适用的程序,事情就很方便了:运行这个程序,让它去完成所需工作。
实际中的计算需求无穷无尽,不可能都有现成的程序。如果面对一个问题,但没有适用的程序,可能就需要编写一个。一般而言,人们需要的不是解决一个具体问题的程序,而是解决一类问题的程序。例如,一个文本编辑器不应该只能编辑出一个具体的文本文件,而应该能用于编辑各种文本文件;Python解释器不是只能执行一个具体的Python程序,而是可以执行所有可能的Python程序。对于求平方根这样的简单问题,人们希望的也不是专用于求某个数(例如2)的平方根的函数,而是能求任何数的平方根的函数。求平方根是一个问题,求2的平方根是求平方根问题的一个实例。人们开发(设计,编写)一个程序,通常是为了解决一个问题,该程序的每次执行能处理该问题的一个实例。
简言之,用计算机解决问题的过程分为两个阶段:程序开发者针对要解决的问题开发出相应的程序,使用者运行程序处理问题的具体实例,完成具体计算(实际上,是计算机按程序的指示完成计算。为简单起见,人们常说程序完成计算,这样说不会引起误解)。开发程序的工作只要做一次,完成的程序可以多次使用,每次处理一个问题实例。当然,对于复杂的程序,完成后通常还需要修改完善,消除错误,升级功能。但这些是后话,无论如何,用计算机解决问题的第一步是开发出能解决问题的程序。

1.1.1程序开发过程

程序开发就是根据面对的问题,最终得到一个可以解决问题的程序的工作过程。真正的问题来自实际,是不清晰和不明确的,而程序是对计算机操作过程的精确描述,两者之间有着非常大的距离。因此,一般而言,程序开发工作需要经过一系列工作阶段才能完成。由于人的认识能力的限制,其中还可能出现反复。图1.1刻画了这一过程中的各个工作阶段,以及实际程序开发的工作流程。
分析阶段:程序开发的第一步是弄清问题。实际中提出(发现)的问题往往是模糊的,缺乏许多细节,是一种含糊的需求。因此,程序开发的第一步就是深入分析问题,弄清其方方面面的情况和细节,将问题严格化,最终得到一个比较详尽的尽可能严格表述的问题描述。在软件开发领域,这一工作阶段通常被称为需求分析。
设计阶段:问题的严格描述仍然是描述性的,而计算机求解是一个操作过程。“一个问题是什么”与“怎样做才能解决它”并不是一件事,在真正编程之前,需要先有一个能解决这个问题的计算过程模型。这种模型包括两个方面,一方面需要表示计算中处理的数据,另一方面必须有求解问题的计算方法,即通常所说的算法。由于问题可能很复杂,其中牵涉的数据不仅可能很多,数据项之间还可能有错综复杂的关系。为了有效操作,就需要把这些数据组织好。数据结构课程的主要内容就是研究数据的组织技术。如何在良好组织的数据结构上完成计算是算法设计的问题,是本书讨论的另一个重点。

《数据结构与算法:Python语言描述》一第1章 绪论_第1张图片

编码阶段:有了解决问题的抽象计算模型,下一步工作就是用某种适当的编程语言实现这个模型,做出一个可能由计算机执行的实际计算模型,也就是一个程序。针对抽象计算模型的两个方面,编程中需要通过语言的各种数据机制实现抽象模型中设计的数据结构,用语言的命令和控制结构实现解决问题的算法。
检查测试阶段:复杂的程序通常不可能一蹴而就,写出的代码中可能有各种错误,最简单的是语法和类型错误。通过人或计算机(语言系统,编译器)的检查,可以发现这些简单错误。经过反复检查修改,最后得到了一个可以运行的程序。
测试/调试阶段:程序可以运行并不代表它就是所需的那个程序,还需要通过尝试性的运行确定其功能是否满足需要,这一工作阶段称为测试和调试。程序运行中可能出现动态运行错误,需要回到前面阶段去修改程序,消除这种错误。也可能发现得到的结果或效果不满足问题需要,这种错误称为逻辑错误。逻辑错误可能反映出编程中的失误,也可能是前面的算法设计有问题,甚至是开始的问题分析没做好。无论如何,发现错误之后,需要设法确定造成问题的原因,回到前面某个工作阶段去做适当的修正。然后根据情况在开发的后续步骤中做相应调整。这些工作需要反复进行,直至得到令人满意的程序。
图1.1和上面的说明阐释了从问题出发,最终得到可用程序的开发过程。在工作的第二和第三个阶段,算法和数据结构的设计和运用技术都扮演着重要角色:在第二个阶段需要设计抽象的数据结构和算法,第三个阶段考虑它们在具体编程语言中的实现。在设计阶段针对具体问题,建立一个可以用计算机实现的问题求解模型,而编码阶段(加上后续工作)真正实现这个求解模型,完成一个可以在计算机上运行的程序。
相对而言,设计阶段的工作更困难一些。其工作基础是问题的说明性描述,有关信息并不能简单地映射到问题的操作性求解过程中,需要人的智力参与。为了完成这一工作,需要考虑被求解问题的性质和特点,参考人们用计算机解决问题的已有经验、已经开发的技术和方法。这方面的一些讨论将是本课程的重要内容。
编码阶段的工作相对容易一些。例如要用Python作为编程语言来解决问题,就需要把已经建立的抽象数据模型映射到Python语言可以表示的结构,把实际问题的抽象求解过程映射到一个用Python语言描述的计算过程。这两方面配合就得到了一个用Python语言写出的解决问题的程序。
下面将通过实例说明程序开发中的一些情况。

1.1.2 一个简单例子

虽然一个问题的说明性描述与其操作性描述表达的是同一个问题,但它们却非常不同。前者说明了需要解决的问题是什么,针对什么样的问题,期望什么样的解;而后者说明通过怎样的操作过程可以得到所要的解。对于一个给定的问题,用某种严格方式描述一个求解过程,且对该问题的每个实例,该过程都能给出解,这个描述就是解决该问题的一个算法。从算法到与之对应的程序,映射关系比较清晰。
现在用一个最简单的问题来说明。假设需要求出任一个非负实数的平方根。这句话是问题的一个非形式描述,工作的第一步就是需要把它严格化。
首先假设实数是一个已经清楚的概念,基于它考虑这个问题。在上面说明中,没有讲清楚的概念是平方根。根据数学中平方与平方根的定义,非负实数 x 的平方根就是满足等式 y×yx 的非负实数y。这是一个严格的数学定义,说明了结果y应该满足的条件。但是,它并没有给出一种从任一x求出满足这个条件的y的方法。
从计算的角度看,上面定义还有一个重大缺陷:对于给定的数值,即使它只包含有穷位小数,其平方根通常也是一个无理数,不能写成数字的有穷表示形式。计算都需要在有穷步内完成,应该是一种有穷过程。因此一般而言,通过计算只能得到实数的平方根的近似值。在考虑求平方根的计算方法(算法)时,这个问题必须考虑,必须把近似误差作为参数事先给定。基于这一看法,上述问题可以修改为:对任意非负实数x,设法找到一个非负实数y,使得| y×y-x |<e,其中e是事先给定的允许误差。
这样就有了问题的一个严格描述。但这个描述是说明性的,说明了需要什么样的y,并没有告诉人们怎么得到这个结果。平方根是一个数学概念,要找到计算平方根的过程性描述(算法),也需要通过数学领域的知识。
人们已经提出了一些求平方根的方法。基本算术课程中介绍过如何求任一正实数的平方根,但在那个方法里需要做试除,不太适合机械进行(可以实现,但比较麻烦)。而求平方根的另一种算法称为牛顿迭代法,描述如下:
0.对给定正实数x和允许误差e,令变量y取任意正实数值,如令y=x;
1.如果y×y与x足够接近,即| y×y-x |<e,计算结束并把y作为结果;
2.取z=(y+x/y)/2;
3.将z作为y的新值,回到步骤1。
首先,这是一个算法,因为它描述了一个计算过程。只要能做实数的算术运算、求绝对值和比较大小,就可以执行上面描述说明的计算过程。
但是,要确定这个算法能求出实数的平方根,还需要证明两个断言:①对任一正实数x,如果算法结束,它一定能给出x的平方根的近似值;②对任意给定的误差e,这个算法一定结束(实际上,这件事还与误差e和计算机实数表示精度有关)。
第一个断言很清楚,步骤1的条件 | y×y-x |<e说明了这个断言成立。第二个断言则需要一个数学证明,证明计算过程中 y 值的序列一定收敛,其极限是 x 的平方根。这样,只要迭代的次数足够多,| y×y-x | 就能任意小,因此对任何允许误差 e,这个循环都能结束。这个问题请读者自己考虑,这里不进一步讨论。
有了上面算法,写出相应Python程序已经不困难了。很容易定义一个完成平方根计算的Python函数,实现上述算法。下面是一个定义:

def sqrt(x):
  y = 1.0
  while abs(y * y – x) > 1e-6:
       y = (y + x/y)/2
  return y

其中变量y的初值为1.0,允许误差为10-6。通过用各种数值测试,可以看到这个函数确实能完成所需要的工作。
从这个简单实例可以看到从问题的描述出发最终得到一个可用程序的工作过程。由于求平方根的问题比较简单,特别是其中涉及的数据只是几个简单实数,数据组织的工作非常简单。下一节的实例将更好展现数据组织的有关情况。
还有一个情况值得注意。在上述例子中,最不清晰的一步就是从平方根的定义到求平方根的算法。算法设计是一种创造性工作,依赖于对问题的认识和相关领域的知识,没有放之四海而皆准的路径可循。计算机科学领域有一个研究方向是算法的设计与分析,计算机教育中有相应课程,其中讨论算法设计和研究的许多经验,总结算法设计中一些规律性的线索和思路。但经验也只是经验,在设计新算法时可以参考,但不能保证有效。算法分析则是分析算法的性质,将在1.3节进一步介绍。

你可能感兴趣的:(《数据结构与算法:Python语言描述》一第1章 绪论)