第3章 计算思维和学科基础 计算思维通过广义的计算来描述各类自然过程和社会过程,从而解决各个学科的问题。本章主要从复杂性、抽象、数学建模、仿真、可计算性等计算思维概念出发,讨论各种问题的建模方法、基本算法思想,以及学科经典问题等内容。 3.1计 算 思 维 3.1.1计算思维的特征 1. 计算工具与思维方式的相互影响 计算科学专家迪科斯彻(Edsger Wybe Dijkstra)说过: “我们使用的工具影响着我们的思维方式和思维习惯,从而也将深刻地影响着我们的思维能力”。计算的发展也影响着人类的思维方式,从最早的结绳计数,发展到目前的电子计算机,人类思维方式发生了相应的改变。如: 计算生物学改变着生物学家的思维方式; 计算博弈论改变着经济学家的思维方式; 计算社会科学改变着社会学家的思维方式; 量子计算改变着物理学家的思维方式。计算思维已成为利用计算机求解问题的一个基本思维方法。 2.计算思维的定义 “计算思维”是哥伦比亚大学(CU)周以真(Jeannette M. Wing)教授提出的一种理论。周以真教授认为: 计算思维是运用计算科学的基础概念去求解问题、设计系统和理解人类行为,它涵盖了计算科学的一系列思维活动。 国际教育技术协会(ISTE)和计算机研究教师协会(CSTA)2011年给计算思维做了一个可操作性的定义,即计算思维是一个问题解决的过程,该过程包括以下特点: (1) 拟定问题,并能够利用计算机和其他工具的帮助来解决问题; (2) 要符合逻辑地组织和分析数据; (3) 通过抽象,如模型、仿真等再现数据; (4) 通过算法思想(一系列有序的步骤),支持自动化的解决方案; (5) 分析问题,找到最有效的方案,并且有效地应用这些方案和资源; (6) 将该问题的求解过程进行推广,并移植到更广泛的问题中。 3. 计算思维的特征 周以真教授在《计算思维》论文中提出了以下计算思维的基本特征。 计算思维是人的,不是计算机的思维方式。计算思维是人类求解问题的思维方法,而不是要使人类像计算机那样思考。 计算思维是数学思维和工程思维的融合。计算科学本质上来源于数学思维,但是受计算设备的限制,迫使计算科学专家必须进行工程思考,不能只是数学思考。 计算思维建立在计算过程的能力和限制之上。需要考虑哪些事情人类比计算机做得好,哪些事情计算机比人类做得好,其中最根本的问题是: 什么是可计算的。 为了有效地求解一个问题,我们可能要进一步问: 一个近似解是否就够了呢?是否允许漏报和误报?计算思维就是通过简化、转换和仿真等方法,把一个看起来困难的问题,重新阐释成一个我们知道怎样解决的问题。 计算思维采用抽象和分解的方法,将一个庞杂的任务分解成一个适合计算机处理的问题。计算思维是选择合适的方式对问题进行建模,使它易于处理。在我们不必理解系统每个细节的情况下,就能够安全地使用或调整一个大型的复杂系统。 根据以上周以真教授的分析可以看出: 计算思维以设计和构造为特征。计算思维是运用计算科学的基本概念进行问题求解、系统设计的一系列思维活动。 3.1.2数学思维的概念 计算思维包含哪些最基本的概念?目前专家们尚无统一的意见。一般来说,计算思维包含数学思维和工程思维两部分。数学思维的基本概念有复杂性、抽象、模型、算法、数据结构、可计算性、一致性和完备性等。 1. 复杂性 大问题的复杂性包括二义性(如语义理解等)、不确定性(如哲学家就餐问题、混沌问题等)、关联(如操作系统死锁问题、大学教师排课问题等)、指数爆炸(如汉诺塔问题、旅行商问题等)、悖论(如罗素理发师悖论、图灵停机问题等)等概念。计算科学专家迪科斯彻曾经指出“编程的艺术就是处理复杂性的艺术”。 (1) 程序的复杂性。德国科学家克拉默(Friedrich Cramer)在著作《混沌与秩序——生物系统的复杂结构》一书中给出了几个简单例子,用于分析程序的复杂性。 【例31】序列A={aaaaaaa…} 这是一个简单系统,相应程序为: 在每个a后续写a。这个短程序使得这个序列得以随意复制,不管多长都可以编程。 【例32】序列B={aabaabaabaab…} 与上例相比,该例要复杂一些,这是一个准复杂系统,但仍可以很容易地写出程序: 在两个a后续写b并重复这一操作。 【例33】序列C={aabaababbaabaababb…} 与上例相似,也可以用短程序来描述: 在两个a后续写b并重复,每当第3次重写b时,将第2个a替换为b。这样的序列具有可定义的结构,可用相应的程序表示。 【例34】序列D={aababbababbbabaaababbab…} 以上案例中的信息排列毫无规律,如果希望编程解决,就必须将字符串全部列出。这就得出一个结论: 一旦程序大小与试图描述的系统大小相提并论,编程就变得没有意义。当系统结构不能描述,或者说描述它的最小算法与系统本身具有相同的信息比特数时,则称该系统为根本复杂系统。在达到复杂系统之前,人们可以编写能解决问题的程序。 (2) 语义理解的二义性。人们视为智力挑战的问题,计算机做起来未必困难; 反而是一些人类觉得简单平常的脑力活动,机器实现起来可能非常困难。例如,速算曾经是人类智力超群的象征,而计算机无论在速度还是准确率上都毫无争议地超过了人类。但是,“为我做一个西红柿炒鸡蛋”这样简单的问题,计算机理解起来就非常困难。因为这个问题存在太多的二义性,如: “西红柿”要什么品种?成熟到什么程度?是否要大小基本相同?“一个”的语义是什么?是1个西红柿还是1斤西红柿?“做”的语义是什么,是“炒”吗?“炒”的语义又是什么,是不停地翻动吗?翻动的频率要多大?炒多长时间,等等。可见概念模糊的命题不可计算。程序语言与自然语言有所不同,最大的区别是自然语言的同一语句在不同语境下有不同的理解,例如,短语“女朋友很重要吗”,可以理解为“女朋友/很重要吗?”,也可以理解为“女朋友很重/要吗?”。程序语言决不允许出现以上歧义。因此,任何一种程序设计语言都有一套规定的程序语法。 二义性问题在程序设计中会经常遇到。在语言中,二义性是一种语法不完善的说明,在程序设计中应当避免这种情况。解决二义性有两种方法: 方法一是设置一些规则,该规则可在出现二义性的情况下指出哪个语法是正确的,它的优点是无须修改文法(可能会很复杂)就能够消除二义性; 方法二是将文法改变成一个强制正确的格式。 (3) 复杂系统的CAP理论。在分布式系统中,一致性(Consistency)指数据复制到系统N台机器后,如果数据有更新,则N台机器的数据需要一起更新; 可用性(Availability)指分布式系统有很好的响应性能。分区容错性(Partition Tolerance)指分布式系统部分机器出现故障时,系统可以自动隔离到故障分区,并将故障机器的负载分配到正常分区继续工作(容错)。埃瑞克·布鲁尔(Eric Brewer)教授指出: 对于分布式系统,数据一致性、系统可用性、分区容错性三个目标(合称CAP)不可能同时满足,最多只能满足其中两个。CAP理论给人们以下启示: 事物的多个方面往往是相互制衡的,在复杂系统中,冲突不可避免。CAP理论是NoSQL数据库的理论基础。 在系统设计中,常常需要在各方面达成某种妥协与平衡,因为凡事都有代价。例如,分层会对性能有所损害,不分层又会带来系统过于复杂的问题。很多时候结构就是平衡的艺术,明白这一点,就不会为无法找到完美的解决方案而苦恼。复杂性由需求所决定,既要求容量大,又要求效率高,这种需求本身就不简单,因此很难用简单的算法解决。 (4) 大问题的不确定性。大型网站往往有成千上万台机器,在这些系统上部署软件和管理服务是一项非常具有挑战性的任务。大规模用户服务往往涉及众多程序模块,很多操作步骤。简单性原则就是要求每个阶段、每个步骤、每个子任务都尽量采用最简单的解决方案。这是由于大规模系统存在的不确定性会增加系统复杂性。即使做到了每个环节最简单,但是由于不确定性的存在,整个系统还是会出现不可控的风险。 【例35】计算科学专家杰夫·迪恩(Jeff Dean)在介绍大规模数据中心遇到的难题时指出: 大部分机器处理请求的平均响应时间为1ms左右,只有1%机器的请求处理时间会大于1s。如果一个请求需要由100个节点机器并行处理,那么就会出现63%的请求响应时间大于1s,这完全不可接受。面对这个复杂的不确定性问题,Jeff Dean和Google公司做了很多工作来解决这个问题。 程序的复杂性来自于大量的不确定性,如需求不确定、功能不确定、输入不确定、运行环境不确定等,这些不确定性无法避免。程序的需求会以各种方式变化,而且往往会向程序员没有预料到的方向发展。由于不确定性的存在,在系统设计中,应当遵循KISS(Keep It Simple,Stupid,保持简单)原则,推崇简单就是美,任何没有必要的复杂都需要避免(奥卡姆剃刀原则)。但是要做到KISS原则并不容易,人们遇到问题时,往往会从各个方面去考虑,其中难免包含了问题的各种细枝末节,这种思维方式会导致问题变得非常复杂。 2. 抽象 (1) 艺术的抽象。在美术范畴内,抽象的表现最简单省力,也最复杂费力。从理论上讲,抽象体现人的主观意识,因此只要画得怪,都可以称为抽象画。有才华的画家认为抽象艺术最美但又最难画,其中包含的艺术内涵太丰富。 【例36】如图31所示,毕加索终生喜欢画牛,年轻时他画的牛体形庞大,有血有肉,威武雄壮。但随着年龄的增长,他画的牛越来越突显筋骨。到他八十多岁时,他画的牛只有寥寥数笔,乍看上去就像一副牛的骨架。而牛外在的皮毛、血肉全部没有了,只剩一副具有牛神韵的骨架了。 图31毕加索画牛的抽象过程 (2) 计算思维的抽象。计算的根本问题是什么能被有效地自动进行。自动化要求对进行计算的事物进行某种程度的抽象,计算思维中的抽象最终要能够利用机器一步步自动执行。为了确保机器的自动化,就需要在抽象过程中进行精确和严格的符号转换和建立数学模型。抽象是对实际事物进行人为处理,抽取所关心的、共同的、本质特征的属性,并对这些事物及其特征属性进行描述,从而大大降低系统元素的数量。计算思维的抽象方法有: 分解、简化、剪枝、替代、分层、模型化、公式化、形式化等。 【例37】为了实现程序的自动化计算,需要将问题抽象为一个数学模型。如将不可计算问题抽象为停机问题; 欧拉将哥尼斯堡七桥问题抽象为“图论”问题; 将解决问题的步骤抽象为算法; 将机器语言翻译抽象为统计语言模型等。 【例38】对数据的抽象方法有: 对数值、字符、图形、音频、视频等信息,抽象为二进制数字; 数据之间的关系有顺序、层次、树形、图形等类型,数据结构就是对这些关系的抽象。程序设计中的抽象方法有: 将数据存储单元地址抽象为变量名; 将复杂的函数程序抽象为简单的应用程序接口(API); 对运行的程序抽象为进程等。 【例39】计算机硬件中的抽象方法有: 将集成电路的设计抽象为布尔逻辑运算; 将不同的硬件设备抽象为HAL(Hardware Abstraction Layer,硬件抽象层); 对I/O设备的操作抽象为文件操作; 将不同体系结构的计算机抽象为虚拟机等。 3. 分解 笛卡儿(René Descartes)在《谈方法》一书中指出: “如果一个问题过于复杂以至于一下子难以解决,那么就将原问题分解成足够小的问题,然后再分别解决”。 (1) 利用等价关系进行系统简化。复杂系统可以看成是一个集合,降低集合复杂性的最好办法是使它有序,也就是按“等价关系”对系统进行分解。通俗地说,就是将一个大系统划分为若干子系统,使人们易于理解和交流。这样,子系统不仅具有某种共同的属性,而且可以完全恢复到原来的状态,从而大大降低系统的复杂性。 (2) 利用分治法思想进行分解。分而治之是指把一个复杂问题分解成若干简单的问题,然后逐个解决。这种朴素的思想来源于人们生活与工作的经验,并且完全适用于技术领域。编程人员采用分治法时,应着重考虑: 复杂问题分解后,每个子问题能否用程序实现?所有程序最终能否集成为一个软件系统?软件系统能否解决这个复杂的问题? 3.1.3工程思维的概念 工程思维的基本概念有效率、资源、兼容性、硬件与软件、模型和结构、时间和空间、编码(转换)、模块化、复用、安全、演化、折中与结论等。 1. 效率 效率始终是计算领域重点关注的问题。例如,为了提高程序的执行效率,采用并行处理技术; 为了提高网络传输效率,采用信道复用技术; 为了提高CPU利用率,采用流水线技术; 为了提高CPU处理速度,采用高速缓存技术等。 【例310】计算领域“优先”技术有: 系统进程优先、中断优先、重复执行的指令优先等,它们体现了效率优先的原则; 而队列、网络数据包转发等,体现了平等优先的原则。效率与平等的选择需要根据实际问题进行权衡分析。如绝大部分算法都采用效率优先原则,但是也有例外。如“树”的广度搜索和深度搜索中,采用了平等优先原则,即保证树中每个节点都能够被搜索到,因而搜索效率很低; 而启发式搜索则采用效率优先原则,它会对树进行“剪枝”处理,因此不能保证树中每个节点都会被搜索到。在实际应用中,搜索引擎同样不能保证因特网中每个网页都会被搜索到,棋类博弈程序也是如此。 但是,效率是一把双刃剑,经济学家奥肯(Arthur M. Okun)在《平等与效率——重大的抉择》中断言: “为了效率就要牺牲某些平等,并且为了平等就要牺牲某些效率”。奥肯的论述同样适用于计算领域。 2. 兼容性 计算机硬件和软件产品遵循向下兼容的设计原则。在计算机产品中,新一代产品总是在老一代产品的基础上进行改进。新设计的计算机软件和硬件应当尽量兼容过去设计的软件系统,兼容过去的体系结构,兼容过去的组成部件,兼容过去的生产工艺,这就是“向下兼容”。计算机产品无法做到“向上兼容”(或向前兼容),因为老一代产品无法兼容未来的系统,只能是新一代产品来兼容老产品。 【例311】老式CRT(Cathode Ray Tube,阴极射线管)采用电子束逐行扫描方式显示图像,而新型LCD(Liquid Crystal Display,液晶显示器)没有电子束,原理上也不需要逐行扫描,一次就能够显示整屏图像。但是为了保持与显卡、图像显示程序的兼容性,LCD不得不沿用老式的逐行扫描技术。 兼容性降低了产品成本,提高了产品可用性,同时也阻碍了技术发展。各种老式的、正在使用的硬件设备和软件技术(如PCI总线、复杂指令系统、串行编程方法等),它们是计算机领域发展的沉重负担。如果不考虑向下兼容问题,设计一个全新的计算机时,完全可以采用现代的、艺术的、高性能的结构和产品,如苹果iPad就是典型案例。 3. 硬件与软件 早期计算机中,硬件与软件之间的界限十分清晰。随着技术发展,软件与硬件之间的界限变得模糊不清了。特兰鲍姆(Andrew S. Tanenbaum)教授指出: “硬件和软件在逻辑上是等同的”。“任何由软件实现的操作都可以直接由硬件来完成,任何由硬件实现的指令都可以由软件来模拟”。某些功能既可以用硬件技术实现,也可以用软件技术实现。 【例312】硬件软件化。硬件软件化是将硬件的功能由软件来实现,它屏蔽了复杂的硬件设计过程,大大降低了产品成本。例如,在x86系列CPU内部,用微指令来代替硬件逻辑电路设计。微指令技术增加了指令设计的灵活性,同时也降低了逻辑电路的复杂性。另外,冯·诺依曼计算机结构中的“控制器”部件,目前已经由操作系统取代。目前流行的虚拟机、虚拟仪表、软件定义网络(Software Defined Network,SDN)等,都是硬件设备软件化的典型案例。 【例313】软件硬件化。软件硬件化是将软件实现的功能设计成逻辑电路,然后将这些电路制造到集成电路芯片中,由硬件实现其功能。硬件电路的运行速率要大大高于软件,因而,软件硬件化能够大大提升系统的运行速率。例如,实现两个符号的异或运算时,软件实现的方法是比较两个符号的值,再经过if控制语句输出运算结果; 硬件实现的方法是直接利用逻辑门电路实现异或运算。视频数据压缩与解压缩、3D图形的几何建模和渲染、数据奇偶检验、网络数据打包与解包等,目前都采用专业芯片处理,这是软件技术硬件化的典型案例。可见硬件和软件的界限可以人为划定,并且经常变化。 【例314】软件与硬件的融合。在TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)网络中,信号比特流通过物理层硬件设备高速传输。而网络层、传输层和应用层的功能是控制比特传输,实现传输的高效性和可靠性等。实际中,应用层的功能主要由软件实现,而传输层和网络层则是软硬件相互融合。如传输层的设备是交换机,网络层的设备是路由器,在这两台硬件设备上,都需要加载软件(如数据成帧、地址查表、路由算法等),以实现对传输的控制。如果只用硬件设备,会使设备复杂化,而且不一定能很好地实现控制功能; 如果只使用软件,程序也会变得很复杂,某些接口功能实现困难,而且程序运行效率较低,这对有实时要求的应用(如数据中心)是致命缺陷。交换机和路由器是软硬件相互融合、协同工作的经典案例。 一般来说,硬件实现某个功能时,具有速度快,占用内存少等优点,但是可修改性差,成本高; 而软件实现某个功能时,具有可修改性好,成本低等优点,但是速度低,占用内存多。具体采用哪种设计方案实现功能,需要对软件和硬件进行折中考虑。 4. 折中与结论 在计算领域产品设计中,经常会遇到: 性能与成本、易用性与安全性、纠错与效率、编程技巧与可维护性、可靠性与成本、新技术与兼容性、软件实现与硬件实现、开放与保护等相互矛盾的设计要求。单方面看,每项指标都很重要,在鱼与熊掌不可兼得的情况下,计算科学专业人员必须做出折中和结论。 【例315】计算机工作过程中,由于电磁干扰、时序失常等原因,可能会出现数据传输和处理错误。如果每个步骤都进行数据错误校验,则计算机设计会变得复杂无比。因此,是否进行数据错误校验,数据校验的使用频度如何,需要进行性能与复杂性方面的折中考虑。例如,在个人微机中,性能比安全性更加重要,因此内存条一般不采用奇偶校验和ECC功能,以提高内存的工作效率; 但是在服务器中,一旦系统崩溃将造成重大损失(如股票交易服务器的崩溃),因此服务器内存条的安全性要求大于工作效率,奇偶校验和ECC是服务器内存必不可少的设计要求。 3.1.4问题求解的方法 利用计算技术解决问题时,一般需要经过以下几个步骤: 一是理解问题,寻找解决问题的条件; 二是对一些具有连续性质的现实问题,进行离散化处理; 三是从问题抽象出一个适当的数学模型,然后设计或选择一个解决这个数学模型的算法; 四是按照算法编写程序,并且对程序进行调试和测试,最后运行程序,直至得到最终答案。 1. 寻找解决问题的条件 (1) 界定问题。解决问题首先要对问题进行界定,弄清楚问题到底是什么,不要被问题的表象迷惑。只有正确地界定了问题,才能找准应该解决的目标,后面的步骤才能正确地执行。如果找不准目标,就可能劳而无获,甚至南辕北辙。 (2) 寻找解题的条件。在“简化问题,变难为易”的原则下,尽力寻找解决问题的必要条件,以缩小问题求解范围。当遇到一道难题时,可以尝试从最简单的特殊情况入手,找出有助于简化问题、变难为易的条件,逐渐深入,最终分析归纳出解题的步骤。 例如,在一些需要进行搜索求解的问题中,一般可以采用深度优先搜索和广度优先搜索。如果问题的搜索范围太大(如棋类博弈),减少搜索量最有效的手段就是“剪枝”(删除一些对结果没有影响的分支问题),即建立一些限制条件,缩小搜索的范围。如果问题错综复杂,可以尝试从多个侧面分析和寻找必要条件; 或者将问题分解后,根据各部分的本质特征,再来寻找各种必要条件。 2. 对象的离散化 计算机处理的对象有一部分本身就是离散化的,如数字、字母、符号等; 但是在很多实际问题中,信息都是连续的,如图像、声音、时间、电压等自然现象和社会现象。凡是“可计算”的问题,处理对象都是离散型的,因为计算机建立在离散数字计算的基础上。所有连续型问题必须转换为离散型问题后(数字化),才能被计算机处理。 【例316】在计算机屏幕上显示一张图片时,计算机必须将图片在水平和垂直方向分解成一定分辨率的像素点(离散化); 然后将每个像素点再分解成红绿蓝(RGB)三种基本颜色; 再将每种颜色的变化分解为0~255(1字节)个色彩等级。这样计算机就会得到一大批有特定规律的离散化数字,计算机也就能够任意处理这张图片了,如图片的放大、缩小、旋转、变形、变换颜色等操作。 3. 解决问题的算法(数学建模) 求解一个问题时,可能会有多种算法可供选择,选择标准首先是算法的正确性、可靠性、简单性; 其次是算法所需要的存储空间和执行速度等。 (1) 问题的抽象描述。遇到实际问题时,首先将其形式化,将问题抽象为一个一般性的数学问题。对需要解决的问题用数学形式描述它,先不要管是否合适。然后通过这种描述来寻找问题的结构和性质,看看这种描述是不是合适,如果不合适,再换一种方式。通过反复地尝试、不断地修正来达到一个满意的结果。遇到一个新问题时,通常都是先用各种各样的小例子去不断地尝试,从中发现问题的关键性质。 (2) 理解算法的适应性。需要观察问题的结构和性质,每个实际问题都有它相应的性质和结构。每种算法技术和思想,如穷举法、分治算法、贪心算法、动态规划、遗传算法、蒙特卡罗算法等,都有它们适宜解决的问题。例如,动态规划适宜解决的问题需要有最优子结构和重复性子问题。一旦我们观察出问题的结构和性质,就可以用现有的算法去解决它。用数学方式表述问题,有利于总结出问题的结构和性质。 (3) 建立算法。建立数学模型时,找出问题的已知条件、求解的目标,以及已知条件和目标之间的联系。算法描述形式有数学模型、数据表格、结构图形、伪代码、程序流程图等。获得了算法并不等于问题可解,问题是否可解还取决于算法的复杂性,即算法所需要的时间和空间在数量级上能否接受。 4. 程序设计 图灵在论文《计算机器与智能》中指出: “如果一个人想让机器模仿计算员执行复杂的操作,他必须告诉计算机要做什么,并把结果翻译成某种形式的指令表。这种构造指令表的行为称为编程”。算法对问题求解过程的描述比程序简单,用编程语言对算法经过细化编程后,可以得到计算机程序,而执行程序就是执行用编程语言表述的算法。 3.1.5数学模型的构建 1. 数学模型 模型是将研究对象通过抽象、归纳、演绎、类比等方法,用适当形式描述的表达方式。简单地说,模型是系统的简化表示,每个模型都是对现实世界的近似模拟,没有完美的模型。模型的类型有实体模型(如汽车模型、城市规划模型等)、仿真模型(如飞行器实验仿真、天气预测模型等)、抽象模型(如数学模型、结构模型、思维模型等)。计算科学经常采用仿真模型和抽象模型来解决问题。 数学模型是用数学语言描述的问题。数学模型可以是一个数学公式、一组代数方程,也可以是它们的某种组合。数学表达式仅仅是数学模型的主要形式之一,但切不可误认为“数学模型就是数学表达式”。数学模型也可以用符号、图形、表格等形式进行描述。 所有数学模型均可转换为算法和程序。数值型问题相对容易建立数学模型,而非数值型问题建模则相对复杂。一些无法直接建立数学模型的系统,如抽象思维、社会活动、人类行为等,需要先将这些问题符号化,然后再建立它们的数学模型。数学模型应用日益广泛的原因在于: 社会生活各个方面日益数字化; 计算技术的发展为精确化提供了条件; 很多无法试验或费用很大的试验(如天气预报),用数学模型进行研究是一条捷径。 2. 数学建模的一般方法 计算领域解决问题时,建立数学模型是十分关键的一步。笛卡儿(René Descartes)设计了一种希望能够解决各种问题的万能方法,它的大致模式是: 第一,把所有问题转换为数学问题; 第二,把所有数学问题转换为一个代数问题; 第三,把所有代数问题归结到解一个方程式。这也是现代数学建模思想的来源。 数学建模方法有以下两大类。一是采用原理分析方法建模,选择常用算法有针对性地对模型进行综合;二是采用统计分析方法建模,通过随机化等方法得到问题的近似数学模型(如语音识别),数学模型出错概率会随计算次数的增加而显著减少。 3. 商品提价的数学建模案例 如果将问题抽象为数学模型,问题就可以用计算方法求解。例如,将“讨价还价”行为看作一场博弈,则可以将问题抽象成数学模型,然后用程序求解。 【例317】商品提价问题建立的数学模型。商场经营者既要考虑商品的销售额、销售量,同时也要考虑如何在短期内获得最大利润。这个问题与商品定价有直接关系,定价低时,销售量大但利润小; 定价高时,利润大但销售量减少。假设某商场销售的某种商品单价为25元,每年可销售3万件。设该商品每件提价1元,则销售量减少0.1万件。如果要使总销售收入不少于75万元,求该商品的最高提价。数学模型建立方法如下。 (1) 分析问题。 已知条件: 单价25元×销售3万件=销售收入75万元; 约束条件1: 每件商品提价1元,则销售量减少0.1万件; 约束条件2: 保持总销售收入不少于75万元。 (2) 建立数学模型。 设最高提价为x元,提价后的商品单价为: (25+x)元; 提价后的销售量为: (30000-1000x/1)件; 则: (25+x)×(30000-1000x/1)≥750000; 简化后数学模型为: (25+x)×(30-x)≥750。 4. 编程求解 求解例317问题的Python程序如下。 1 2 3 4 >>>from sympy import symbols, solve >>>x = symbols('x') >>>f = solve((25+x)*(30-x) >= 750) >>>print('x的取值范围是:', f) x的取值范围是: (0 <= x) & (x <= 5) # 导入第三方包-符号计算 # 设置x为符号 # 计算不等式取值范围 # 打印结果 # x大于或等于0,而且小于或等于5 对以上问题编程求解后: x≤5,即提价最高不能超过5元。 3.2建 模 案 例 建模是对问题求解的抽象过程。有人认为,建立数学建模需要专业的数学知识,其实很多数学模型并不涉及高深的数学知识,如“平均收入”的安全计算。即便是对复杂系统的研究(如细胞自动机),有时只需要几条简单的规则。 3.2.1囚徒困境: 博弈策略建模 1. 博弈论概述 如果有两人以上参与,且双方可以通过不同策略相互竞争的游戏,而且一方采用的策略会对另一方的行为产生影响,这种情况称为博弈。1944年,冯·诺依曼和奥斯卡·摩根斯特恩(Oskar Morgenstern)发表了《博弈论和经济行为》著作,首次介绍了博弈论(Game Theory),他们希望博弈论能为经济问题提供数学解答。 博弈论的基本元素有参与者、行动、信息、策略、收益、均衡和结果。博弈包括同时行动和顺序行动两种类型。在同时行动博弈中,参与者在不了解对方行动的情况下采取行动(如囚徒困境、工程竞标等)。顺序行动博弈采用轮流行动方式,先行动者的动作会明确告知后行动者,博弈参与者轮流行动(如象棋比赛、商业谈判、学术辩论等)。 囚徒困境是两个囚徒之间的一种特殊博弈,它说明了为什么在合作对双方都有利时,保持合作也非常困难。囚徒困境也反映了个人最佳选择而并非团体最佳选择。虽然囚徒困境只是一个模型,但现实中的价格竞争、商业谈判等,都会频繁出现类似的情况。 2. 囚徒困境问题描述 1950年,兰德公司的梅里尔·弗勒德(Merrill Flood)和梅尔文·德雷希尔(Melvin Dresher)根据博弈论拟定出相关困境的理论,后来由艾伯特·塔克(Albert Tucker)以“囚徒困境”的命题进行阐述。经典的囚徒困境描述如例318所示。 【例318】警方逮捕了A、B两名嫌疑犯,但没有足够证据指控二人有罪。于是警方分开囚禁嫌疑犯,单独与二人见面,并向双方提供以下相同的选择(如图32所示)。 图32囚徒困境的博弈 (1) A、B都认罪并检举对方(互相背叛),则A、B同判3年(A=3,B=3)。 (2) A不认罪并检控B(A背叛),B认罪(B合作),则A获释,B判5年。 (3) B不认罪并检控A(B背叛),A认罪(A合作),则B获释,A判5年。 (4) A、B都不认罪(互相合作),则A、B均判1年(A=1,B=1)。 【例319】用Python程序实现囚徒困境的博弈,代码如下。 1 2 3 4 5 6 7 8 9 10 11 12 13 14# E0319.py while True: a = input('A,你认罪吗?【1=认罪;2=不认;0=退出】:') b = input('B,你认罪吗?【1=认罪;2=不认;0=退出】:') if a == '1' and b == '1': print('A、B都得判3年,唉') elif a == '2' and b == '1': print('A判0年,B判5年,唉') elif a == '1' and b == '2': print('A判5年,B判0年,唉') elif a == '2' and b == '2': print('A判1年,B判1年。') else: break# 【建模-囚徒困境】 # 建立循环判断 # 输入A的博弈策略 # 输入B的博弈策略 # A、B都认罪 # 打印博弈结果 # A不认罪,B认罪 # 打印博弈结果 # A认罪,B不认罪 # 打印博弈结果 # A、B都不认罪 # 打印正确选择 # 否则 # 退出循环,结束程序 >>>...(输出略)# 程序运行结果 3. 囚徒的策略选择困境 囚徒到底应该选择哪个策略,才能将自己的刑期缩至最短?两名囚徒由于隔绝监禁,并不知道对方的选择; 即使他们能够交谈,也未必能够尽信对方,或者会存心欺骗对方。就个人理性选择而言,检举对方(背叛)得到的刑期,总比沉默(合作)要低。困境中两名理性囚徒的可能会做出如下选择。 (1) 若对方沉默,背叛会让我获释,所以我会选择背叛。 (2) 若对方背叛我,我也要指控对方才能得到较低刑期,所以也选择背叛。 两个囚徒的理性思考都会得出相同的结论: 选择背叛。结果二人都要服刑。 在囚徒困境博弈中,如果两个囚徒选择合作,双方都保持沉默,总体利益会更高。而两个囚徒只追求个人利益,都选择背叛时,总体利益反而较低,这就是“困境”所在。 4. 囚徒困境的数学建模 根据囚徒困境的基本博弈思想,可以建立一个数学模型,然后进行编程求解。在社会学和经济学中,经常采用博弈形式分析各种论题,以下是囚徒困境建模的一般形式。 (1) 策略符号化。对囚徒困境中的各种行为以T、R、P、S符号表示,将囚徒由于各种选择而获得的收益和支付转换为数值,这就获得了表31所示的符号表。 表31囚徒困境的符号表 符号分数英文中文说明 T5Temptation背叛收益单独背叛成功所得 R3Reward合作报酬共同合作所得 P1Punishment背叛惩罚共同背叛所得 S0Suckers受骗支付单独背叛所得 从表31可见: 5>3>1>0,从而得出不等式: T>R>P>S。一个经典的囚徒困境问题必须满足这个不等式,不满足这个条件的问题就不是囚徒困境。但是,以整体获分最高而言,将得出以下不等式: 2R>T+S(如2×3 >5+0)或2R>2P(如2×3 >2×1),由此可见合作(2R)比背叛(T+S或2P)得分高。如果重复进行囚徒困境的博弈,参与者的策略将会从注重“T>R>P>S”转变为注重“2R>T+S”(即“合作优于背叛”),尤其要避免2P(两人均背叛)的出现。 (2) 建立收益和支付矩阵。假设根据以下规则来确定博弈双方的收益和支付值: 一人背叛,一人合作时,背叛者得5分(背叛收益),合作者得0分(受骗支付); 二人都合作时,双方各得3分(合作报酬); 二人都背叛时,各得1分(背叛惩罚)。 由此得到的收益和支付矩阵如表32所示。 表32囚徒困境的收益和支付矩阵表 囚徒的收益和支付矩阵以符号表示的策略 策略A合作A背叛策略A合作A背叛 B合作A=3,B=3A=5,B=0B合作R,RT,S B背叛A=0,B=5A=1,B=1B背叛S,TP,P (3) 建立数学模型。由表32可以建立以下数学模型: A=R,B=R时,A=3,B=3 A=T,B=S时,A=5,B=0 A=S,B=T时,A=0,B=5 A=P,B=P时,A=1,B=1 5. 囚徒困境的博弈策略 密歇根大学的罗伯特·阿克斯罗德(Robert Axelrod)为了研究囚徒困境问题,在1979年组织了一场计算机程序比赛。比赛设定了两个前提: 一是每个人都是自私的; 二是没有权威干预个人决策,每个人完全按照自己利益最大化进行决策。他研究的主要问题是: 人为什么要合作?什么时候合作,什么时候不合作?如何使别人与自己合作? 参加博弈的有14个程序,每个程序循环对局300次,得分最高的是加拿大学者阿纳托尔·拉波波特(Anatol Rapoport)编写的“一报还一报”程序。这个程序的博弈策略是: 第一次对局采取合作策略,以后每次对局都采用和对手上一次相同的策略。即对手上一次合作,我这次就合作; 对手上一次背叛,我这次就背叛。 6. 囚徒困境模型的应用 在人类社会或大自然都可以找到类似囚徒困境的例子。经济学、政治学、动物行为学、进化生物学等学科,都可以用囚徒困境模型进行研究分析。 【例320】商业活动中会出现各种囚徒困境的案例。以广告竞争为例: 两家公司互相竞争,两家公司的广告互相影响,即一公司的广告被顾客接受则会夺取对方公司的市场份额。如果两家公司同时发出质量类似的广告,则收入增加很少但成本增加较大。两家公司都面临两种选择: 好的策略是互相达成协议,减少广告开支(合作); 差的策略是增加广告开支,设法提升广告质量,压倒对方(背叛)。 3.2.2机器翻译: 统计语言建模 长期以来,人们一直梦想能让机器代替人类翻译语言、识别语音、理解文字。计算科学专家从1950年开始,一直致力于研究如何让机器对语言做最好的理解和处理。 1. 基于词典互译的机器翻译 早期人们认为只要用一部双向词典和一些语法知识就可以实现两种语言文字间的机器互译,结果遇到了挫折。例如,将英语句子Time flies like an arrow(光阴似箭)翻译成日语,然后再翻译回来时,竟变成了“苍蝇喜欢箭”; 英语句子The spirit is willing but the flesh is weak(心有余而力不足)翻译成俄语,然后再翻译回来时,竟变成了The wine is good but the meat is spoiled(酒是好的,肉变质了)。 这些问题出现后,人们发现机器翻译并非想象的那么简单,人们认识到单纯地依靠“查字典”的方法不可能解决机器翻译问题,只有在对语义理解的基础上,才能做到真正的翻译。因此,机器翻译当时被认为是一个完整性问题,即为了解决其中一个问题,你必须解决全部的问题,哪怕是一个简单和特定的任务。 2. 基于语法分析的机器翻译 1957年,乔姆斯基(Avram Noam Chomsky)提出了“形式语言”理论。形式语言是用数学方法研究自然语言(如英语)和人工语言(如程序语言等)的理论,乔姆斯基提出了形式语言的表达形式和递归生成方法。 【例321】用形式语言表示短句“那个穿红衣服的女孩是我的女朋友”。 假设: S=短句,线条=改写,V=动词,VP=动词词组,N=名词,NP=名词词组,D=限定词,A=形容词,P=介词,PP=介词短语。用形式语言表示的短句如图33所示。 【例322】上例用形式语言表示程序的条件语句: if x==2 then {x=a+b}。用形式语言表示的程序语句如图34所示。 图33用形式语言表示的短句 图34用形式语言表示的程序语句 乔姆斯基提出“形式语言”理论后,人们更坚定了利用语法规则进行机器翻译的信念。为了解决机器翻译问题,人们想到了让机器模拟人类进行学习: 这就需要让机器理解人类语言,学习人类的语法,分析语句等。遗憾的是,依靠计算机理解自然语言遇到了极大的困难。机器翻译的某些语句,在语法上很正确,但是语义上无法理解或存在矛盾。 【例323】The pen is in the box: 钢笔在盒子里。 【例324】The box is in the pen: 盒子在钢笔里(正确翻译: 盒子在围栏里)。 3. 基于概率统计的机器翻译 两种不同语言交谈的人们,怎样根据信息推测说话者的意思呢?以语音识别为例,当检测到的语音信号为o1,o2,o3时,根据这组信号推测发送的短语是s1,s2,s3。显然,这是在所有可能的短语中,找到可能性最大的一个短语。用数学语言描述就是: 在已知o1,o2,o3,…的情况下,求概率P(o1,o2,o3,…|s1,s2,s3,…)达到最大值的短语s1,s2,s3,…。即 P(o1,o2,o3,…|s1,s2,s3,…)·P(s1,s2,s3,…)(31) 式中: P(o1,…|s1,…)是条件概率,它表示在s1发生的条件下,o1发生的概率,其中o1和s1具有相关性,读作“在s1条件下o1的概率”; P(s1,s2,s3,…)是联合概率,表示s1、s2、s3等事件同时发生的概率,其中s1、s2、s3是相互独立的事件。因此,在式(31)中,P(o1,o2,o3,…|s1,s2,s3,…)表示某个短语s1,s2,s3,…被读成o1,o2,o3,…的可能性(概率值)。而P(s1,s2,s3,…)表示字串s1,s2,s3,…成为合理短语的可能性(概率值)。 如果把s1,s2,s3,…当成中文,把o1,o2,o3,…当成对应的英文,那么就能利用这个模型解决机器翻译问题; 如果把o1,o2,o3,…当成手写文字得到的图像特征,就能利用这个模型解决手写体文字的识别问题。 4. N元统计语言模型的数学建模 1972年,美国计算科学专家贾里尼克(Fred Jelinek)用两个隐马尔可夫模型(Markov Model,一种隐含未知参数的统计模型)建立了统计语音识别数学模型。马尔可夫模型假定下一个词出现的概率只与前一个词有关,这大大减少了计算量。利用马尔可夫模型时,需要先对一个海量语料库进行统计分析,这个过程称为“训练”,它需要把任意两个词语之间关联的概率计算出来。在实际操作中,还会牵涉很多其他复杂的细节。 统计语言模型是基于概率的模型,计算机借助大容量语料库的概率参数,估计出自然语言中每个短语出现的可能性(概率),而不是判断该短语是否符合语法。常用统计语言模型有: N元文法模型(Ngram Model)、隐马尔可夫模型、最大熵模型等。统计语言模型依赖于单词的上下文(本词与上一个词和下一个词的关系)概率分布。例如,当一个短语片段为“他正在认真……”时,下一个词可以是“学习、工作、思考”等,而不可能是“美丽、我、中国”等。学者们发现,许多词对后面出现的词有很强的预测能力,英语这类有严格语序的语言更是如此。汉语语序较英语灵活,但是这种约束关系依然存在。 【例325】对短语“南京市长江大桥”进行分词时,可以切分为“南京市/长江/大桥”和“南京/市长/江大桥”,我们会认为前者的切分更合理,因为“长江大桥”与“江大桥”两个分词中,后者在语料库中出现的概率很小。所以,同一个短语中出现若干不同的切分方法时,希望找到概率最大的那个分词。 统计语言模型描述了任意语句S属于某种语言集合的可能性(概率P(S))。例如: P(他/认真/学习)=0.02(概率值),P(他/认真/读书)=0.03,P(他/认真/坏)=0等。这里并不要求语句S在语法上是完备的,该模型需对任意语句S都给出一个概率统计值。 假设一个语句S中第wi个词出现的概率,依赖于它前面的N-1个词,这样的语言模型称为Ngram模型(N元语言统计模型)。语句S出现的概率P(S)等于每个词(wi)出现的概率相乘。语句S出现的概率P(S)可展开为 P(S)=P(w1)P(w2|w1)P(w3| w1 w2)…P(wn|w1 w2…wn-1)(32) 其中,P(w1)表示第1个词w1出现的概率; P(w2|w1)是在已知第1个词的前提下,第2个词出现的概率; 其余以此类推。不难看出,词wn的出现概率取决于它前面所有词。为了预测词wn的出现概率,必须已知它前面所有词的出现概率。从计算来看,各种可能性太多,太复杂了。因此人们假定任意一个词wi的出现概率只与它前面的词wi-1有关(马尔可夫假设),于是问题得到了很大的简化。这时S出现的概率就变为 P(S) = P(w1)P(w2|w1)P(w3| w1 w2)…P(wn|w1w2…wn-1)(33) 接下来的问题是如何估计P(wi|wi-1)。有了大量文本语料库后,这个问题变得很简单,只要数一数这对词(wi-1,wi)在统计文本中出现了多少次,以及wi-1本身在同样文本中前后相邻出现了多少次。简单地说,统计语言模型的计算思维就是: 短句S翻译成短句F的概率是短句S中每个单词翻译成F中对应单词概率的乘积。根据式(33),可以推导出常见的N元模型(假设只有4个单词的情况)如下。 一元模型为P(w1w2w3w4)=P(w1)P(w2)P(w3)P(w4); 二元模型为P(w1w2w3w4)=P(w1)P(w2|w1)P(w3|w2)P(w4|w3); 三元模型为P(w1w2w3w4)=P(w1)P(w2|w1)P(w3|w1w2)P(w4|w2w3)。 N元统计语言模型应用广泛。如以下案例所示,前面语句出现的概率,大大高于后面的语句,因此,根据语言统计模型,正确的选择是前面的语句。 【例326】中文分词。对语句“已结婚的和尚未结婚的青年”进行分词时: P(已/结婚/的/和/尚未/结婚/的/青年)>P(已/结婚/的/和尚/未/结婚/的/青年)。 【例327】机器翻译。对语句The box is in the pen进行翻译时: P(盒子在围栏里)P(盒子在钢笔里)。 【例328】拼写纠错。P(about fifteen minutes from)>P(about fifteen minuets from)。 【例329】语音识别。P(I saw a van)P(eyes awe of an)。 【例330】音字转换。将输入的拼音gong ji yi zhi gong ji转换为汉字时: P(共计一只公鸡>P(攻击一致公鸡)。 统计语言模型的机器翻译过程如图35示。 图35统计语言模型的机器翻译过程 5. 三元统计语言模型的应用 Ngram模型的缺点在于当N较大时,需要规模庞大的语料文本统计库(如Google Ngram语料库为1TB左右,本书的配套教学资源中附有小型专题语料库)来确定模型的参数。目前使用的Ngram模型通常是N=2的二元语言模型,或是N=3的三元语言模型。以三元模型为例,可以近似地认为任意词wi的出现概率只与它前面两个词有关。 20世纪80年代,李开复博士用统计语言模型成功地开发了世界上第一个大词汇量连续语音识别系统Sphinx。哈尔滨工业大学研发的微软拼音输入法也是基于三元统计语言模型。三元统计语言模型现在仍然是实际应用中表现最佳的语言模型。据调查,目前市场上的语音听写系统和拼音输入法都是基于三元模型实现的。 早期时,基于概率的“愚蠢”翻译方法居然比语言学家设计的规则系统翻译得更好,这让每个人都感到惊讶。但是,概率统计的翻译方法不可能做到百分之百准确。目前,更好的方法是采用深度神经网络进行机器翻译。 说明: 在线简单语句翻译案例,参见本书配套教学资源程序F31.py。 3.2.3平均收入: 安全计算建模 1. 什么是安全多方计算 为了说明什么是安全多方计算(SMC),先介绍几个实际生活中的例子。 【例331】很多证券公司的金融研究组用各种方法,试图统计基金的平均仓位,但得出的结果差异较大。为什么不直接发送调查问卷呢?因为基金经理都不愿意公开自己的真实仓位。那么如何在不泄露每个基金真实数据的前提下,统计出平均仓位的精确值呢? 以上案例具有以下共有特点: 一是两方或多方参与基于他们各自私密输入数据的计算; 二是他们都不想其他方知道自己输入的数据。问题是在保护输入数据私密性的前提下如何实现计算? 这类问题是安全多方计算中的“比特承诺”问题。 2. 简单安全多方计算的数学建模 【例332】假设一个班级的大学同学毕业10年后聚会,大家对毕业后同学们的平均收入水平很感兴趣。但基于各种原因,每个人都不想让别人知道自己的真实收入数据。是否有一个方法,在每个人都不会泄露自己收入的情况下,大家一块算出平均收入呢? 方法是: 同学们围坐在一桌,先随便挑出一个人,他在心里生成一个随机数X,加上自己的收入N1后(S←X+N1)传递给邻座,旁边这个人在接到这个数(S)后,再加上自己的收入N2后(S←S+N2),再传给下一个人,依次下去,最后一个人将收到数加上自己的收入后传给第一个人。第一个人从收到数里减去最开始的随机数,就能获得所有人的收入之和。该和除以参与人数就是大家的平均收入。以上方法的数学模型为 SR=(S-X)/n 其中,SR为平均收入; S为全体同学收入累计和; X为初始随机数; n为参与人数。 以上是美国数学教授大卫·盖尔(David Gale)提出的案例和数学模型。 3. 合谋问题 在例332的方法中,存在以下几个问题: (1) 模型假设所有参与者都是诚实的,如果有参与者不诚实,则会出现计算错误。 (2) 第1个人可能谎报结果,因为他是“名义上”的数据集成者。 (3) 如果第1个人和第3个人串通,第1个人把自己告诉第2个人的数据同时告诉第3个人,那么第2个人的收入就被泄露了。 问题(1)和问题(2)属于游戏策略问题。问题(3)是否存在一种数学模型,使得对每个人而言,除非其他所有人一起串通,否则自己的收入不会被泄露呢?将在7.3.5节的“同态加密”中讨论数据加密计算问题。 4. 姚氏百万富翁问题 “百万富翁问题”是安全多方计算中著名的问题,它由华裔计算科学家、图灵奖获得者姚期智教授提出: 两个百万富翁想要知道他们谁更富有,但他们都不想让对方知道自己财富的任何信息。如何设计这样一个安全协议?姚期智教授在1982年的国际会议上提出了解决方案,但是这个算法的复杂度较高,它涉及“非对称加密”算法。 百万富翁问题推广为多方参与的情况是: 有n个百万富翁,每个百万富翁Pi拥有Mi百万(其中1≤Mi≤N)的财富,在不透露富翁财富的情况下,如何进行财富排名。 【例333】姚氏百万富翁问题的商业应用。假设张三希望向李四购买一些商品,但他愿意支付的最高金额为x元; 李四希望的最低卖出价为y元。张三和李四都希望知道x与y哪个大。如果x>y,他们都可以开始讨价还价; 如果xy或x=y; 如果无记号则x', c) else: hanoi(n-1, a, c, b) hanoi(1, a, b, c) hanoi(n-1, b, a, c) n=int(input('请输入汉诺塔的盘子数:')) hanoi(n, 'a', 'b', 'c')# 【复杂性-汉诺塔】 # 定义递归函数,n为盘子数,a,b,c为柱子名 # 如果只有一个盘子 # 将盘子从a柱移到c柱 # 否则 # 递归,将a柱n-1号盘子移到b柱,c柱为辅助柱 # 递归,将a柱最底层最大的盘子移到c柱 # 递归,将b柱n-1号盘子移到c柱,a柱为辅助柱 # 接收用户输入的数据,并转换为整数 # 调用递归函数hanoi( ) >>>请输入汉诺塔的盘子数:3 a-->c a-->b c-->b a-->c...(输出略)# 程序运行结果(运算时间呈指数增长,测试 # 表明,15个盘子的运算时间约为5分钟) 汉诺塔递归求解程序虽然非常简单,但是难以理解,需要反复琢磨。汉诺塔有64个盘子时,盘子最少移动次数为264-1=1.8×1019。由此可知,并不是所有问题都可以计算,即使是可计算问题,也要考虑计算量是否超过了目前计算机的计算能力。 说明: 汉诺塔动画案例,参见本书的配套教学资源程序F33.py。 3.3.4不完备性与可计算性 计算理论研究的三个核心领域为自动机、可计算性和复杂性。通过“计算机的基本能力和局限性是什么?”这一问题将这三个领域联系在一起。在计算复杂性理论中,将问题分成容易计算和难计算。在可计算理论中,把问题分成可解和不可解。 1. 哥德尔不完备性定理 20世纪以前,大部分数学家认为所有问题都有算法,关于计算问题的研究就是找出解决各类问题的算法。1928年,德国著名数学家戴维·希尔伯特(David Hilbert)提出一个问题: 是否有一个算法能对所有的数学原理自动给予证明。这个美好的希望不久就被打破了,1931年,奥地利数学家哥德尔(Kurt Gdel)给出了证明: 任何无矛盾的公理体系,只要包含初等数论,则必定存在一个不可判定命题,用这组公理不能判定其真假; 或者说: 一个理论如果不自相矛盾,那么这种性质在该理论中不可证明。 哥德尔不完备性定理说明,任何一个形式系统,它的一致性和完备性不可兼得。或者说,如果一个形式系统是一致的,那么这个系统必然是不完备的,二者不可兼得。 不完备性也可以用一个古老的哲学观点“一个人不可能完全理解自身”进行验证: 如果你能做到完全理解自身,你就可以预知自己在未来十秒钟内做什么,而刻意做出不符合预测的行为就可以推翻“你能够完全理解自身”这个前提。 2. 图灵机的不完备性 哥德尔不完备性定理说明,在任何一个数学系统内,总有一些命题的真伪无法通过算法来确定。总是存在这样的函数,由于过于复杂以致没有严格定义的、逐步计算的过程能够根据输入值来确定输出值。也就是说,这种函数的计算超出了任何算法的能力。 图灵提出图灵机模型后也发现了有些问题图灵机无法计算。例如,定义模糊的问题,如“人生有何意义”; 或者缺乏数据的问题,如“明天彩票的中奖号是多少”,它们的答案无法计算出来; 还有一些定义完美的问题也是不可计算的,如“停机问题”。 3. 什么是可计算性 物理学家阿基米德曾经宣称: “给我足够长的杠杆和一个支点,我就能撬动地球”。在数学上也同样存在类似的问题: 是不是只要给数学家足够长的时间,通过“有限次”简单而机械的演算步骤,就能够得到最终答案呢?这就是“可计算性”问题。 什么是可计算的?什么又是不可计算的呢?要回答这一问题,关键是要给出“可计算性”的精确定义。20世纪30年代,一些著名数学家和逻辑学家从不同角度分别给出了“可计算性”概念的确切定义,为计算科学的发展奠定了重要基础。 可计算的问题都可以通过自然数编码的方法,用函数的形式表示。因此可以通过定义在自然数集上的“直观可计算函数”(也称为一般递归函数)来理解“可计算性”的概念。凡是从某些初始符号串开始,在有限步骤内得到计算结果的函数都是直观可计算函数。 1935年,丘奇(Alonzo Church)为了定义可计算性,提出了λ演算理论。丘奇认为,λ演算可定义的函数与直观可计算函数相同。 1936年,哥德尔、丘奇等定义了递归函数。丘奇指出: 一切直观可计算函数都是递归函数。简单地说,计算就是符号串的变换。凡是可以从某些初始符号串开始,在有限步骤内可以得到计算结果的函数都是一般递归函数。可以从简单的、直观上可计算的一般函数出发,构造出复杂的可计算函数。1936年,图灵也提出: 图灵机可计算函数与直观可计算函数相同。 一个显而易见的事实是: 数学精确定义的直观可计算函数都是可计算的。问题是直观可计算函数是否恰好就是这些精确定义的可计算函数呢?对此丘奇认为: 凡直观可计算函数都是λ可定义的; 图灵证明了图灵可计算函数与λ可定义函数是等价的,著名的“丘奇图灵论题”(丘奇是图灵的老师)认为: 任何能直观计算的问题都能被图灵机计算。如果证明了某个问题使用图灵机不可计算,那么这个问题就是不可计算的。 由于直观可计算函数不是一个精确的数学概念,因此丘奇图灵论题不能加以证明,也因此称为“丘奇图灵猜想”,该论题被普遍假定为真。 4. 哪些数不可计算? 如何判断一个数是否可以计算?图灵在一篇论文中提出: “当一个实数所有的位数,包括小数点后的所有位,都可以在有限步骤内用某种算法计算出来,它就是可计算数; 如果一个实数不是可计算数,那它就是不可计算数”。例如,圆周率π可以在有限时间内计算到小数点后的任何位置,所以π是可计算数。 1975年,计算科学专家格里高里·蔡廷(Gregory Chaitin)提出了一个有趣的问题: 选择任意一种编程语言,随机输入一段代码,这段代码能成功运行并且会在有限时间里终止(不会无限运行下去)的概率是多大?他把这个概率值命名为“蔡廷常数”。目前已经在理论上证明了,永远无法求出“蔡廷常数”的值。 理论上,任何一段程序的运行结果,只有终止和永不终止两种情况。可终止程序的数量一定占有全体程序总数的一个固定比例,所以这个停机概率一定存在,它的上限为1,下限为0。在真实环境中,任意编程语言书写的程序有无穷多个,要想用统计方法得到这个概率值,需要的程序测试也是无穷多次,显然蔡廷常数是一个不可计算数。 5. 不可计算问题的类型 不可计算的问题有以下类型: 第一类是理论意义上不可计算问题,由丘奇图灵论题确定的所有非递归函数都是不可计算的。具体问题有: 停机问题、蔡廷常数、彭罗斯拼图(Penrose Diagram)、蒂博尔·拉多(Tibor Radó)提出的忙碌海狸函数BB(n)、不定方程的整数解(希尔伯特第10个问题,也称为丢番图方程)等,这些问题都是计算能力的理论限制。第二类是现实计算机的速度和存储空间都有一定限制,如某个问题在理论上可计算,但是计算时间如果长达几百年,那么这个问题实际上还是无法计算。如汉诺塔问题、长密码破解问题、旅行商问题、大数因式分解问题等。第三类是现实意义上难以计算的问题,如“西红柿炒鸡蛋”这样一个概念模糊的命题也不可计算。 3.3.5P=NP?计算科学难题 1. P问题 有些问题是确定性的,如加、减、乘、除计算,只要按照公式推导,就可以得到确定的结果,这类问题是P问题。P(Polynomially,多项式)问题是指算法能在多项式时间内找到答案的问题,这意味着计算机可以在有限的时间内完成计算。 对一个规模为n的输入,最坏情况下(穷举法),P问题求解的时间复杂度为O(nk)。其中k是某个确定的常数,我们将这类问题定义为P问题,直观上,P问题是在确定性计算模型下的易解问题。P问题有: 计算最大公约数、排序问题、图搜索问题(在连通图中找到某个对象)、单源最短路径问题(两个节点之间的最短路径)、最小生成树问题(连通图中所有边权重之和最小的树)等,这些问题都能在多项式时间内解决。 2. NP问题 NP(Nondeterministic Polynomially,非确定性多项式)问题中的N指非确定的算法。有些问题是非确定性问题,如寻找大素数问题,目前没有一个现成的推算素数的公式,需要用穷举法进行搜索,这类问题就是NP问题。NP问题不能确定是否能够在多项式时间内找到答案,但是可以确定在多项式时间内验证答案是否正确。 【例340】验证某个连通图中的一条路径是不是哈密尔顿(Hamilton)回路很容易,而问某个连通图中是否不存在哈密尔顿回路则非常困难,除非穷举所有可能的哈密尔顿路径,否则无法验证这个问题的答案,因此这是一个NP问题。 3. NPC问题 如果任何一个NP问题能通过一个多项式时间算法转换为某个其他NP问题,那么这个NP问题就称为NPC(NPComplete,NP完全)问题。NPC是比NP更难的问题。 【例341】对任意的布尔可满足性问题(SAT),总能写成以下形式: (x1∨x2∨x3)∧(x4∨x5∨xn)… 其中,∨表示逻辑或运算; ∧表示逻辑与运算; 表达式中x的值只能取0或者1。那么当x取什么值时,这个表达式为真?或者根本不存在一个取值使表达式为真?这就是SAT问题,任意SAT是一个典型的NPC问题。 一个简单的SAT问题,如: (x1∨x2∨x3)∧(x1∨x2)∧(x2∨x3),当x1=0,x2=0,x3=任意值(0或1)时,表达式的值为真,-表示逻辑取反运算。 NPC问题目前没有多项式算法,只能用穷举法逐一检验,最终得到答案。但是穷举算法的复杂性为指数关系,计算时间随问题的复杂程度成指数级增长,很快问题就会变得不可计算了。目前已知的NPC问题有3000多个,如布尔可满足性问题、国际象棋N皇后问题、密码学中大素数分解问题、多核CPU流水线调度问题、哈密尔顿回路问题、旅行商问题(Travelling Salesman Problem,TSP)、最大团问题、最大独立集合问题、背包问题等。 4. NPhard问题 除NPC问题外,还有一些问题连验证解都不能在多项式时间内解决,这类问题被称为NPhard(NP难)问题。NPhard太难了,围棋或象棋的博弈问题; 怎样找到一个完美的女朋友等,都是NPhard问题。计算科学中难解问题之间的关系如图316所示。 图316计算科学中的难解问题 【例342】棋类博弈问题。考察棋局所有可能的博弈状态时,国际跳棋有1031种博弈状态,香农在1950年估计出国际象棋的博弈状态大概有10120种; 中国象棋的博弈状态估计有10150种; 围棋的博弈状态达到了惊人的10360种。事实上大部分棋类的计算复杂度都呈指数级上升。作为比较,目前可观测到宇宙中的原子总数估计有1078~1082个。 5. P=NP?问题 直观上看计算复杂性时: P>>import math >>>math.factorial(25) 15511210043330985984000000# 导入标准模块-数学 # 计算25的阶乘值 # 所有路径数=1.5×1025,计算量发生组合爆炸 说明: 一个简化的旅行商问题求解案例,参见本书配套教学资源程序F38.py。 2010年,英国伦敦大学奈杰尔·雷恩(Nigel Raine)博士在《美国博物学家》发表论文指出,蜜蜂每天都要在蜂巢和花朵之间飞来飞去,在不同花朵之间飞行是一件很耗体力的事情,因此蜜蜂每天都在解决“旅行商问题”。雷恩博士利用人工控制的假花进行实验,结果显示,不管怎样改变假花的位置,蜜蜂稍加探索后,很快就可以找到在不同花朵之间飞行的最短路径。如果能理解蜜蜂是怎样做到这一点的,将对解决旅行商问题有很大帮助。 旅行商问题应用广泛,如在车载GPS中,经常需要规划行车路线,如何做到行车路线距离最短,就需要对旅行商问题进行求解; 在印制电路板(Printed Circuit Board,PCB)设计中,如何安排众多的导线使线路距离最短,也需要对旅行商问题进行求解。旅行商问题在其他领域也有广泛的应用,如物流运输规划、网络路由节点设置、遗传学领域、航空航天等领域。 3.4.4单向函数: 公钥密码的基础 1. 单向函数的概念 在现实世界中,单向函数的例子非常普遍。例如,把盘子打碎成数百块碎片是很容易的事情,然而要把所有碎片再拼成一个完整的盘子,却是非常困难的事情。例如,将挤出的牙膏塞回牙膏管子,这要比把牙膏挤出来困难得多。类似地,将两个大素数相乘要比将它们的乘积因式分解容易得多。 【例344】计算41×71=2911很容易,但是将2911因数分解为41和71却很难。 2. 单向函数的假设 单向函数听起来没什么问题,但若严格地按数学定义,并不能从理论上证明单向函数的存在,因为这将意味着计算科学中最具挑战性的猜想P=NP成立,而目前的NP完全性理论不足以证明存在单向函数。即便是这样,还是有很多函数看起来像单向函数,我们能够有效地计算它们,而且至今还不知道有什么办法能容易地求出它们的逆,因此假定存在单向函数。 最简单的单向函数就是整数相乘。不管两个多大的整数,都可以很容易地计算出它们的乘积; 但是对于两个100位的素数之积而言,很难在合理的时间内分解出两个素数因子。 图321单向陷门函数求解示意图 3. 单向陷门函数 单向陷门函数是一类特殊的单向函数,它在一个方向上易于计算而反方向难于计算。但是,如果你知道某个秘密,就能很容易地在另一个方向上计算这个函数。也就是说,已知x,易于计算f(x); 而已知f(x),却难于计算x,如图321所示。然而,如果有一些秘密信息y,一旦给出f(x)和y,就很容易计算出x。例如,钟表拆卸和安装就像一个单向陷门函数,很容易把钟表拆成数百个小零件, 而把这些小零件组装成能够工作的钟表却非常困难; 然而,通过某些秘密信息(如钟表装配手册),就能把钟表安装还原。 4. 单向陷门函数在密码中的应用 单向函数不能直接用作密码体制,因为如果用单向函数对明文进行加密,即使是合法的接收者也不能还原出明文,因为单向函数的逆运算非常困难。密码体制更关心的是单向陷门函数。毫不夸张地说,公钥密码体制的设计就是寻找单向陷门函数。注意,单向陷门函数不是单向函数,它只是对那些不知道陷门的人表现出单向函数的特性。著名的RSA(Rivest,Shamir,Adleman)密码体制就是根据单向陷门函数而设计。 3.4.5哲学家就餐问题: 死锁控制 1965年,迪科斯彻(Dijkstra)在解决操作系统的“死锁”问题时,提出了“哲学家就餐”问题,他将问题描述为: 有五位哲学家,他们的生活方式是交替地进行思考和进餐。哲学家们共用一张圆桌,分别坐在周围的五把椅子上,圆桌上有五个碗和五支餐叉, 图322哲学家就餐问题 如图322所示。平时哲学家进行思考,饥饿时哲学家试图取左、右最靠近他的餐叉,只有在他拿到两支餐叉时才能进餐; 进餐完毕,放下餐叉又继续思考。 在哲学家就餐问题中,有如下约束条件: 一是哲学家只有拿到两支餐叉时才能吃饭; 二是如果餐叉已被别人拿走,哲学家必须等别人吃完之后才能拿到餐叉; 三是哲学家在自己未拿到两支餐叉前,不会放下手中已经拿到的餐叉。 哲学家就餐问题用来说明在并行计算中(如多线程程序设计),多线程同步时产生的问题; 它还用来解释计算机死锁和资源耗尽问题。 哲学家就餐问题形象地描述了多进程以互斥方式访问有限资源的问题。计算机系统有时不能提供足够多的资源(CPU、内存等),但是又希望同时满足多个进程的使用要求。如果只允许一个进程使用计算机资源,系统效率将非常低下。研究人员采用一些有效的算法,尽量满足多进程对有限资源的需求,同时尽量减少死锁和进程饥饿现象的发生。 当五个哲学家都左手拿着餐叉不放,同时去取他右边的餐叉时,就会无限等待下去,引起死锁现象发生。在实际问题中,常用的解决方法是资源加锁,使资源只能被一个程序或一段代码访问。当一个程序想要使用的资源被另一个程序锁定时,它必须等待资源解锁。当多个程序涉及加锁资源时,在某些情况下仍然有可能发生死锁。 计算机死锁没有完美的解决方法。解决系统死锁需要频繁地进行死锁检测,这会严重降低系统的运行效率,得不偿失。由于死锁并不经常发生,大部分操作系统采用了“鸵鸟算法”,即尽量避免发生死锁,一旦真正发生了死锁,就假装什么都没有看见,重新启动死锁的程序或系统。可见鸵鸟算法是为了效率优先而采用的一种折中策略。 3.4.6两军通信: 信号不可靠传输 网络通信能不能做到理论上可靠?特南鲍姆教授在《计算机网络》一书中提出了一个经典的“两军通信”问题。 如图323所示,一支A军在山谷里扎营,在两边山坡上驻扎着B军。A军比两支B军中的任意一支都要强大,但是两支B军加在一起就比A军强大。如果一支B军单独与A军作战,它就会被A军击败; 如果两支B军协同作战,他们能够把A军击败。两支B军要协商一同发起进攻,他们唯一的通信方法是派通信员步行穿过山谷,通过山谷的通信员可能躲过A军的监视,将发起进攻的信息传送到对方B军; 但是通信员也可能被山谷中的A军俘虏,从而将信息丢失(信道不可靠)。问题: 是否存在一个方法(协议)能实现B军之间的可靠通信,使两军协同作战而取胜?这就是两军通信问题。 图323两军通信问题: B军之间如何实现安全可靠的通信 特南鲍姆教授指出,不存在一个通信协议使山头两侧的B军达成共识。因为在信道不可靠的情况下,永远无法确定最后一次通信是否送达了对方。如果更深入一步讨论,当B军通信员被A军截获,并且通信内容被A军修改后(黑客攻击),情况将会变得更加复杂。可见,网络通信是在不可靠的环境中实现尽可能可靠的数据传输。 两军通信问题本质上是一致性确认问题,也就是说通信双方都要确保信息的一致性。因特网TCP(传输控制协议)采用“三次握手”进行通信确认,这是一个通信安全和通信效率兼顾的参数。我们说“TCP是可靠通信协议”,仅仅是说它成功的概率较高而已。 两军通信问题在计算机通信领域应用广泛,如数据的发送方如何确保数据被对方正确接收; 在计算机集群系统中,如果在指定时间内(如数秒)没有收到计算节点的“心跳”信号时,怎样确定对方是“宕机”了,还是通信网络出现了问题; 在密码学中,最困难的工作是如何将密钥传送给接收方(密钥分发),如果在理论或实际中有一个安全可靠的方法将密钥传送给接收方,则加密和解密系统就显得多此一举。 习题3 31简要说明什么是计算思维。 32简要说明计算领域解决问题的主要步骤。 33简要说明计算领域哪些问题不可计算以及如何解决这类问题。 34写出“石头剪刀布”游戏的博弈策略矩阵。 35简要说明如何利用统计语音数学模型,将宠物狗的肢体语言翻译为人类语言。 36简要说明汉诺塔问题递归求解过程中,盘子的移动有哪些规律。 37一个黑白图像局部区域有1000个随机数据,如10001101…,现在需要1转换为0,将0转换为1,请用图灵机编写程序实现以上功能(要求进行程序注释)。 38“现代计算机与图灵机在计算性能上是等价的”这句话正确吗? 39简要说明不可计算的问题有哪些类型。 310简要说明P问题。 311简要说明NP(非确定性多项式)问题。 312简要说明不可计算问题的解决方法。 313简要说明计算复杂性理论的研究内容。 314实验: 参考例319的程序案例,实现囚徒困境的博弈。 315实验: 参考例339的程序案例,用递归求解汉诺塔问题。