本书封面贴有清华大学出版社防伪标签，无标签者不得销售。

版权所有，侵权必究。举报：010-62782989，beiqinquan@tup.tsinghua.edu.cn。

图书在版编目（CIP）数据

趣味数学和Python编程／赵乘骥编著．—北京：清华大学出版社，2021.2

ISBN 978-7-302-56706-6

Ⅰ．①趣…　Ⅱ．①赵…　Ⅲ．①中学数学课－初中－教学参考资料②软件工具－程序设计－初中－教学参考资料　Ⅳ．①G634.603②G634.673

中国版本图书馆CIP数据核字（2020）第203475号

责任编辑：文　怡

封面设计：王昭红

责任校对：李建庄

责任印制：杨　艳

出版发行：清华大学出版社

网　　址：http://www.tup.com.cn，http://www.wqbook.com

地　　址：北京清华大学学研大厦A座

邮　　编：100084

社总机：010-62770175

邮　　购：010-83470235

投稿与读者服务：010-62776969，c-service@tup.tsinghua.edu.cn

质量反馈：010-62772015，zhiliang@tup.tsinghua.edu.cn

课件下载：http://www.tup.com.cn，010-83470236

印刷者：北京富博印刷有限公司

装订者：北京市密云县京文制本装订厂

经　　销：全国新华书店

开　　本：186mm×240mm

印　　张：17.25

字　　数：210千字

版　　次：2021年2月第1版

印　　次：2021年2月第1次印刷

印　　数：1～2500

定　　价：59.00元

产品编号：088023-01

内容简介

本书介绍了一些适合初中学生的有趣的数学内容，例如，怎么用程序图文并茂地展示NBA球员的投篮位置和成功率，同班同学中有人生日相同的概率是多大，什么样的屋顶排水最快，等等。书中的问题都经过精心选择和安排，与初中数学教学的进度同步，同时又用Python程序动态化表示这些数学过程并生成一个个GIF动图，使得数学的学习过程变得好玩了，可以激发学习兴趣。

本书适用于初中阶段计算机教学，数学教师和家长也可以将本书作为Python入门的参考书，书中展示的生成GIF动图的方法可以用于制作数学课件。

作者简介

赵乘骥，暨南大学能源电力研究中心研究员，南京爱布谷网络科技有限公司总经理。在视频处理领域有20余年的研发经验，领导团队研发了按需实时转码服务系统、视频协作系统。对使用人工智能进行视频分析和基于区块链的视频分发系统有浓厚兴趣，工作中经常使用Python进行数据分析和测试。近年因为孩子逐渐成长开始琢磨如何进行少儿编程和人工智能教学。

前言

本书最初是我教自己孩子学习Python编程时编写的，我想把它写得有趣一点儿，同时也包含比初中数学教材略微难一点的数学内容，希望他能在编程和数学上都有所提高，所以数学内容的进度基本与初中数学教材同步。家长和教师可以将本书作为教材，每周和孩子学习两小时编程，一起和孩子做一些有意义的事情就是最好的教育，家长们不用喋喋不休地说理或是苦口婆心地教育，你和他一起学，一起练，自己做好了，孩子自然就做好了，我一直是这么认为的。

为了让本书内容有趣，我做了两方面的努力：一是查阅了大量数学史书籍，包括数学史故事和人物介绍；二是试图让涉及的数学内容都能用图形描述，很多时候，学完数学知识，再编程产生一个对应的GIF动图，可以使数学内容变得生动，过程变得直观。我把对应产生的动图拿给我父亲（一名优秀的初中数学教师）看，他对我这个做法赞不绝口，我内心也有点儿得意。最终小朋友们自己会通过编程制作这些动图，他们还可以在QQ或微信里晒出这些动图，相信他们会和我一样收获非常多的信心，进而产生浓厚的兴趣。

本书假定读者没有任何编程基础，任何具有初一数学知识的人都可以阅读本书，并一步步独立完成本书内容。

无论家长还是学校在使用本书时，都要强调孩子自己用Python语言独立完成全部编程实验，在实验中学习数学知识，最终把学习成果总结成实验报告，目的是：

（1）培养小朋友的编程思维和实际动手能力。

（2）锻炼小朋友运用数学知识的能力，把数学和生活实践联系起来。

（3）激发小朋友学习数学的兴趣。

本书的绝大部分内容都由我儿子亲自尝试过，也在学校试教过，其难度完全可以被小朋友接受，程序也全部调试通过，家长可以放心使用。

由于我的水平有限，不妥之处敬请读者批评指正。

赵乘骥　博士

2020年12月

1　Python和一个数学天才黑了约会网站的故事

Python从一诞生就吸引了人们的注意，多年来，它的地位一直在稳步上升。2014年，一名黑客改行的新闻工作者凯文（Kevin Lee Poulsen）报道了一名数学家兼黑客克里斯·麦金莱（Chris McKinlay）用黑科技收获爱情的真实故事，并火遍了互联网，让人们进一步认识到原来Python可以这么强大。故事源于2012年6月初的一天凌晨，35岁的加州大学洛杉矶分校（UCLA）数学系博士生克里斯一边用计算机做博士论文研究，一边打开一个新窗口查看他在约会网站OKCupid的收件箱。自从9个月前和女友分手后，他一直试图通过OKCupid寻找一份新的爱情。这个网站以通过算法自动匹配约会对象而出名，但他给那些被OKCupid算法吹嘘为很般配的女孩发了许多短信，大多石沉大海，在9个月的时间里他一共才和6个女孩见了面，都是一次之后就没了下文。这天，当他盯着那份孤独的个人档案，突然间意识到哪里出了问题。

OKCupid会员们需要回答从政治、宗教、家庭到爱情等各种各样的一系列问题。平均而言，会员需要从数千个问题中选出350个问题，对每一个问题，都写出一个自己的标准答案，同时以5分制打分的方式对其他回答进行评价。系统会根据用户对问题的回答，自动计算潜在约会对象间的匹配度并优先推荐匹配度高的人。

但从数学上讲，克里斯与洛杉矶女性的匹配度是非常低的。OKCupid的算法只使用两个潜在约会者都决定回答的问题来进行匹配，而克里斯选择的问题被证明是不受女性待见的。当他滚动搜索匹配对象时，只有不到100名女性出现在90%的匹配指数之上。而洛杉矶大约有200万女性，其中约8万人是OKCupid用户，在一个匹配度等于可见性的网站上，他基本上就是一个幽灵，这就是问题所在。

首先，他必须提高匹配人数。如果通过统计抽样，可以确定哪些问题对他喜欢的女性有重要意义，他就可以建立一个全新的个人档案，诚实地回答这些问题，而忽略其他问题。这样，他就可以匹配到洛杉矶每一个可能适合他的女性，同时自动过滤掉那些不适合的。要达到这个效果，首先他需要数据。为此他建立了12个OKCupid机器人账户，并编写了一个Python脚本来管理它们。Python脚本搜索年纪和他相仿的女性，访问她们的页面，并从她们的个人资料中搜寻种族、身高、吸烟与否、星座等内容。此外，为了找到调查问题的合适的回答，他不得不采取一些非常的手段。OKCupid可以让一个用户看到其他人对某个问题的回复，但前提是该用户也回答过这些问题。于是，克里斯设置了他的机器人随机地、简单地回答每一个问题，因为他并没有使用这些虚拟的机器人资料来吸引任何一个女性，所以机器人的回答本身无关紧要，重要的是要把这些女性的回答收集到一个数据库中。

然而，在收集了大约1000份个人资料后，他遇到了第一个问题。OKCupid已经有一个系统来防止这种数据收集机器人，它可以很容易地发现这些数据收割机。一个接一个地，他的机器人被禁止了。他意识到应该训练机器人模仿人的行为，这样才能防止被禁。

克里斯想起了他的朋友萨姆·托里西，一位神经科学家，托里西当时在教克里斯音乐理论，作为交换，克里斯教托里西高级数学课程。托里西也在OKCupid上有账户，他同意在自己的计算机上安装监视软件来收集他在该网站的使用习惯。有了托里西和自己的这些行为数据，克里斯编程他的机器人，模拟人的点击率和打字速度。他从家里带来了第二台计算机，把它插到数学系的宽带交换机上，这样它就可以24小时不间断地让这些机器人干活。三周后，他从20 000名女性那里收集了600万个问题和回答。这时候克里斯已经顾不上他的论文了，他大多数晚上都睡在自己的小隔间里，要睡觉的时候就在桌子上放一张薄床垫。

拿到数据还只是第一步，他必须在调查数据中找到一种模式，即根据女性的相似性对她们进行大致分组。他尝试了几种不同的方法，在尝试了贝尔实验室发明的一种称为K-mode聚类的算法后终于获得了想要的突破。根据自己的问题和回答，20 000名女性被聚类成7个统计上不同的群体。

他重新给他的机器人分配了任务，这次训练的样本略有差异，机器人收集了5000名在过去一个月登录了OKCupid的洛杉矶和旧金山的女性。然后又用K-mode算法聚类，证实了它们仍然以类似的方式聚类。统计抽样的方法看来是有效的。

现在，所有女性被他分成了7个群体，他只需要决定哪个群体最适合他就行。他查看了每个群体的统计特性。一组太年轻，两组太老，另一组太偏基督教。一组25岁左右的女性群体让他流连忘返，她们是经济独立的音乐家或艺术家，这看起来是个理想的群体。他决定在这里进行淘金。但是，邻近的一个群体的女性看起来也相当酷，她们略微成熟一点，专业从事创造性工作，比如编辑和设计师。最后他决定两个组都试试，为此他把两组分别命名为A、B组，针对这两组，又各优化了一份个人档案。

之后，他又通过文本挖掘这两个类别来了解她们感兴趣的内容，发现教育是一个共同的热门话题，因此他写了一篇突出自己作为数学教授的简介。不过，重要的部分是调查问题。他挑出了在这两组人中最受欢迎的500个问题并诚实地回答了这些问题，他不想未来的情侣关系建立在计算机自动编造的谎言之上。但是他也机灵地让他的计算机计算出每个问题的重要性，使用一种称为自适应增强的机器学习算法得出最佳权重，简而言之，哪个问题重要就回答哪个。

最后，两份个人档案新鲜出炉了，一个封面上他正在攀岩，而另一个封面上，他正在演唱会上弹吉他。对于问题的重要性，他完全遵循计算机的指示。一切设置好以后，他在OKCupid上搜索洛杉矶的女性，并按匹配百分比排序。喔呼！在顶部，满页匹配率为99%的女性，一页又一页，在翻阅了一万名来自洛杉矶各地的女性之后，他的匹配度仍然维持在90%以上。

匹配的女性完全找出来了，他还需要一步——引起她们的注意。在OKCupid上，当有人浏览某会员的网页时，该会员会收到通知，所以他又用Python写了一个新的程序来访问最匹配他的女孩的网页，并按年龄自动循环：

周一是1000名41岁的女性，周二是1000名40岁的女性；两周后，当访问完27岁这个群体的女性后，又循环再来。很快，女孩们回访了他的个人资料，每天约400人，消息随之滚滚而来。一位女士写道：“到目前为止，我还没有遇到任何人拥有这样的匹配度，我觉得你的个人资料很有趣。”

“我想我们有很多共同点，也许不是数学，但肯定还有很多其他有趣的东西！”，另一个人写道。

克里斯的匹配搜索的数学部分已经完成了。现在，他得离开办公室去约会。

6月30日，克里斯在加州大学洛杉矶分校的体育馆淋浴后，开着他那辆破旧的日产汽车穿过城镇，开始了他的第一次数据挖掘出来的约会。希拉是一位年轻的网页设计师。他们在Echo公园的一家咖啡馆共进午餐。但在约会结束时，双方都清楚地意识到，他们之间并无火花。第二天，他约会一个来自B组的迷人的博客编辑。他原计划在Echo公园湖边浪漫散步，但发现湖水正在疏浚，她最近一直在读普鲁斯特的书，对自己的生活感到沮丧。他觉得气氛有点压抑。

第三次约会也是B组的。他在韩国城的一家酒吧里和艾莉森见面了，她是一个正在学习编剧的学生，肩上文着一个斐波那契螺旋线。他们喝了韩国啤酒，聊得似乎很嗨，第二天在自己的小隔间里醒来，克里斯的头仍然很痛，他在OKCupid里给艾莉森发了一条信息，但她再也没有回信。被拒绝让他觉得很不好受，好在他仍然受到很多女性的关注，每天收到20多条主动示好的信息。信息太多，他只对那些表现出幽默感或在简介中显示出有趣特性的人做出回应，其他就只好忽略了。相比以前需要来回交换四五条信息才能成功安排一次约会，现在他只需要一个回复：“你看起来真的很酷，想见面吗？”

刚开始的一些约会他还颇费心思去安排。但是，约会队列中的女孩太多，他不得不提高效率，经常需要一天安排两次约会。他为此制定了一套规则来完成他的马拉松式爱情搜索。比如，不再喝酒，如果不合适就当机立断，不听音乐会，不看电影，保持注意力在彼此，不进行任何会让注意力转移的其他活动，等等。

经过一个月和这两个群体的人不停地约会，他发现自己为了约会东区的女孩群体而花费太多的时间在高速公路上。他删除了他在A组的个人资料。相应地，效率提高了，但结果还是一样。当夏天接近尾声时，他已经进行了55次约会，每次都仔细地记录在实验室的笔记本上。但只有三次约会后续又进行了第二次约会；只有一次导向了第三次约会。克里斯开始怀疑他的算法，也许网撒得有点大？

就在这时，28岁的艺术家、正在UCLA攻读美术硕士学位的克里斯蒂娜·王甜（Christine Tien Wang）发过来了一条消息。她在UCLA附近寻找6英尺（1英尺≈0.3048米）高、蓝眼睛男孩时突然看到了克里斯的资料，他们是91%的匹配。他在校园的雕塑花园约见了她，从那里他们走到一家寿司店。他立刻感觉到了彼此间的电流，他们谈论书籍、艺术、音乐。当她坦白承认在给他发短信之前，她对自己的个人资料做了一些调整时，他把自己的爱情黑客行为都告诉了她。“太黑了，太坏了”，她雀跃地说，“不过我喜欢。”

这是第88号候选人的第一次约会，接着是第二次，然后是第三次，两周后，他们都暂时关闭了他们的OKCupid账户。一年后，克里斯向王甜求婚，她愉快地答应了。

对王甜来说，克里斯的黑客行为是一个有趣的故事，但所有的数学和编码只是他们故事的序幕，一段感情中真正的“入侵”（hacking）发生在相遇之后。克里斯说：“我认为，我所做的只是对网站上所有人所做的事情进行了一个算法化、数据驱动和基于机器学习的改进，每个人都试图创建一个最佳的形象，我只是用Python结合大数据设计了一个。”

2　计算机基础知识和Python开发工具安装

【本章摘要】

数学内容：无。

编程内容：计算机组成、原理及基础知识，Python集成开发环境及组件的安装方式，运行用Python编写的有趣的Demo。

2.1　计算机基础知识

我们如果把家里的台式计算机拆开，会发现图2-1中列出的各种部件，在拆卸前务必要断电，而且最好先摸一摸家里和地直接相连的金属家具，把身上的静电放掉，以免静电损害敏感的部件。

如果把计算机和人来类比一下的话，那么：

CPU——人脑，负责计算和指挥一切配件和外设。

硬盘——我们日常使用的记事本，记录我们记不住的东西。

内存——大脑中负责记忆的脑细胞。

电源——胃，吃东西然后提供能量。

主板——就像人的身体框架，连接有手、脚、脑袋等功能性的部件，计算机主板就专门连接计算机的各个功能模块。

图2-1　计算机硬件系统组成

计算机要工作，离不开操作系统，离不开一些核心的应用软件。计算机软件系统构成如图2-2所示。操作系统可以以一个快餐店（如肯德基）来类比，制作快餐的设备、服务员、点餐计算机以及餐厅，这些可以看成计算机的各种硬件设备，如果没有一个规章制度和服务流程来让各个岗位的人员协同工作，顾客走进快餐店说：“服务员，点餐”，服务员们都站着，不知所措，因为没有人告诉他们每个人的具体职责。餐厅经理看到这种情况，他就会为每个员工安排恰当的工作，谁接单，谁切菜，谁做厨师，并且会规定接单以后，订单如何传给快餐制作人员，制作完成以后如何传递给柜台，这些规定、流程就是肯德基的操作系统。计算机的操作系统就是背后让键盘、硬盘、网卡、CPU这些硬件协同工作的程序、约定，有了操作系统，计算机才可能被软件程序员开发出各种应用，为大众所用。

图2-2　计算机软件系统构成

操作系统是一种特殊的用于控制计算机（硬件）的程序（软件）。它是计算机底层的系统软件，负责管理、调度、指挥计算机的软硬件资源使其协调工作，没有它，任何计算机都无法正常运行。它在资源使用者和资源之间充当中间人的角色。举个例子，一个用户（也可以是程序）将一个文件存盘，操作系统就会开始工作：管理磁盘空间的分配，将要保存的信息由内存写到磁盘等。当用户要运行一个程序时，操作系统必须先将程序载入内存，当程序执行时，操作系统会让程序使用CPU。

从上面的描述可以得出，计算机操作系统大致包括5个方面的管理功能：进程与处理器管理、作业管理、存储管理（硬盘和内存）、设备管理和文件管理。这里面提到了进程，什么是进程呢？大致可以说，你要计算机完成的一件具体的事，就是一个进程。比如你同时打开了浏览器浏览网页，播放器播放一个视频，Office编辑一个文档，那么你至少开启了3个进程，分别做三件不同的事。

操作系统目前有Windows、Linux、UNIX、Android、iOS等，我们日常使用的约90%的计算机上装有Windows操作系统，约80%的手机上安装Android操作系统，苹果手机使用iOS操作系统。

Python则是一门程序设计语言，在图2-2中属于“程序语言和开发环境”这一类。2020年3月，TIOBE公布了编程语言排行榜数据，Python继续占据第三名的位置，使用比率首次超过10%，如图2-3所示。

图2-3　计算机编程语言排名

Python语言的最大优势在于全栈开发、网络爬虫、数据挖掘、人工智能、Web开发、系统网络运维、数据分析和计算、图形程序开发等方向都可以使用Python语言来完成，你可以从现在开始用Python编程进行数学实验，直到读博士时用Python研究博士课题，它无所不能。

2.2　Python开发工具和安装

本书所有程序都使用Python 3.7.5开发，因此建议安装这个版本或者更新的Python版本。Python安装需要以下五个步骤。

（1）打开浏览器，进入https://www.python.org/downloads/网站（见图2-4）下载与你的操作系统匹配的Python版本。本书以Windows操作系统为例，如果你使用Linux或Mac OS，请自行尝试安装。

图2-4　下载地址

（2）选择32位或64位版本的安装包（见图2-5），注意你需要到Windows的控制面板（控制面板→设置→系统→关于）去查看你的操作系统是64位还是32位，如果你不知道，就选64位操作系统，大概率不会错。

图2-5　选择正确版本的安装包

（3）双击下载可执行文件，按照提示安装。在安装时，注意要勾选“Add Python 3.7 to PATH”复选框，这一步勾选很重要，否则需要手动去设置环境变量。

（4）安装好以后，在开始菜单里面找到“命令提示符”选项，通常在系统的“开始→Windows系统”里面，输入“python --version”，然后按Enter键（注意是两个减号），如果返回Python 3.7.5，那么Python安装成功，然后再输入“pip --version”，如果也返回对应版本号，那么Python的包管理工具也安装成功，这两个都需要安装成功，见图2-6。如果出现问题，可以上网查找解决办法，通常会一次安装成功。

图2-6　验证Python是否安装成功

（5）还是在这个命令提示符界面下，输入“python -m turtledemo”（注意，只输入引号里面的内容）。这个demo包里面包含很多有趣又好看的demo。

可以用于Python开发的集成开发环境有好几个，下面介绍Eclipse+Pydev，本书主要用的就是这个开发环境。

2.3　集成开发环境Eclipse+Pydev

集成开发环境Eclipse是一个跨平台的编程开发环境，它在Windows、Mac OS、Linux等不同操作系统上都可以运行，可以用于Java、C/C++以及Python开发，具有图形化的集成开发环境，可以极大地提高编程的效率，因此被广泛使用。Eclipse有一个插件叫Pydev，安装后支持Python开发，安装步骤如下：

（1）安装Java，进入https://www.java.com/网站下载并安装JDK。（2）安装Eclipse，进入https://www.eclipse.org/网站下载并安装Eclipse IDE，Eclipse有多个风格的界面，可以选择适合自己的Eclipse版本，如Eclipse Classic或Eclipse for Java。

（3）安装Pydev，运行Eclipse之后，单击主菜单栏的help→Install new Software。在弹出的对话框中单击Add按钮。在Name中输入“Pydev”，在Location中输入“http://pydev.org/updates”，见图2-7。

图2-7　设置Pydev插件网址

（4）在已有的软件中选择Pydev，单击Next按钮后程序会自动到官网下载安装一系列组件。见图2-8，安装完毕后就进入下一步。

（5）安装好Pydev后，需要配置Python解释器，见图2-9。在Eclipse菜单栏中，选择Windows→Preferences选项。在对话框中，选择PyDev→Interpreter-Python选项。单击New按钮，选择python.exe的路径，打开后显示出一个包含很多复选框的窗口，单击OK按钮。

到这一步开发环境就配置成功，可以开始Python编程了。

图2-8　下载安装Pydev

图2-9　配置Python解释器

（6）选择File→New→Projects选项，创建一个新的项目，见图2-10。选择PyDev→PyDevProject选项，输入项目名称，比如hello，然后单击Finish按钮，完成项目创建。

图2-10　创建项目

（7）为上一步创建的项目添加一个文件hello.py，见图2-11。

在Eclipse的左边Pydev Package Explorer中找到刚才创建的项目hello，右击，在弹出的快捷菜单中，选择New→File选项，创建一个新的文件hello.py，注意要带.py扩展名。单击Finish按钮，命名新文件，见图2-12。

图2-11　创建新文件

图2-12　命名新文件

（8）编辑新文件hello.py。在文件中输入一句简单的打印语句print（"hello world"），完成以后，单击主菜单栏上的Run就可以执行。注意要在英文输入状态下输入整句话，否则执行时会报如下错误：

3　Python基础

【本章摘要】

数学内容：无。

编程内容：介绍Python编程最基本知识，集成开发环境Eclipse的使用，Python基本数据类型和基本语句介绍。

3.1　计算机如何执行Python程序

Python安装成功以后，它包含一些最基本的组件，如一个解释器和支持的库。解释器最基本的功能就是逐行理解我们写的程序，并顺序执行，如果出错，就停止执行并报错。这是我们程序员看到的执行过程，实际上，当程序执行时，Python解释器会先将我们的一行行代码编译成字节码指令（通常为.pyc扩展名），然后把字节码发送给一个Python虚拟机（Python Virtual Machine）去执行。

为什么要翻译成字节码？为什么要由虚拟机去执行？CPU不可以直接执行吗？好问题！

（1）为什么要翻译成字节码？

就像两个语言不同的人在交流时需要翻译，Python是为方便人和机器交流而研发的一种语言，显然它一定要让人类好写，好懂，这样一来，人类是方便了，效率也高了，但机器不喜欢这种形式的语言，因此有了翻译成字节码的过程，翻译好了，这个字节码就是Python虚拟机能读懂的东西。

（2）为什么要虚拟机？

计算机有很多种类，家里可能有台式机、笔记本电脑、平板电脑，这些不同种类的计算机使用了不同的CPU芯片，性能不同，价格差异很大，这就像汽车厂商会有各自不同的发动机一样，这些CPU上运行的操作系统也有很大不同，比如苹果电脑使用Mac OS，大部分台式机使用Windows。为了让Python在这些不同的操作系统上都能运行起来，定义了一个工作方式统一的虚拟机，不管底层的操作系统和CPU如何千变万化，虚拟机的工作方式保持不变，把具体底层CPU的适配交给操作系统。这就是虚拟机的作用。回过头来看，我们安装的时候，强调一定要选择和操作系统匹配的安装程序，也就是这个原因！

3.2　程序员如何执行Python程序

3.2.1　交互式执行语句

在Windows的开始菜单中找到Python IDLE，双击就会出现以下界面。

可以在这里面直接执行Python语句，只要符合Python语法，都可以执行，按回车键就能立即得到结果，例如，输入print（“hello world”），立即就返回“hello world”。这种方法不好之处就是它不会把代码保存到一个文件中，但是可以作为尝试代码合法性的一种好方法，通过交互提示模式尝试代码，通常是搞清楚一段代码到底起到什么作用的最快方式，比如：‘what’*7，看起来很费解，马上尝试一下：

3.2.2　使用命令行执行文件

可以使用任何编辑器编辑一个Python文件，比如a.py，注意扩展名一定要是.py，然后在系统自带的命令行中输入python a.py.，就会执行这个python文件。这种方法很有用，尤其是如果你用Python写了一些文件处理的程序，比如递归地删除文件夹内的某一类文件等，基本上都会使用这种方式执行。使用这种方式执行时有几个需要注意的地方：

（1）文件扩展名一定要用.py。

（2）执行Python的环境变量PATH一定要设置到指向python.exe文件。这个环境变量的设置，我们在安装Python时，强调了要勾选Add Python 3.7 to PATH复选框。如果没有自动添加，也不复杂，可以通过“控制面板”→“系统”→“关于”→“系统信息”→“高级系统设置”→“环境变量”把python.exe的路径手动添加到PATH中，python.exe到哪里去找呢？当然是你当时把Python 3.7安装到哪里，就到哪里去找。

3.2.3　在集成开发环境里面执行文件

本书推荐使用PyCharm和Eclipse+Pydev，在前面章节安装集成开发环境时介绍过。

3.3　Python程序初体验

我们现在来执行一小段程序，打开集成开发环境，创建一个PyDev Project，命名为lab1，然后在lab1这个Project中创建一个新文件，名字叫test.py，注意事项如下。

（1）总是先创建Project，然后往Project中添加文件。Project是工程、项目的意思，创建一个项目就好比建一栋房子，当然需要很多模块，这一个个文件就是独立的模块，如门、窗、墙等。

（2）创建Project时有很多选择，我们为什么要创建Pydev项目而不是Java、Maven，等等？我们在一开始安装Eclipse时就提到，Eclipse是一个可以进行Java、C/C++以及Python开发的集成开发环境，创建Pydev项目就是告诉Eclipse接下来的项目种类是Python项目，请用Python的解释器来执行创建的程序。

（3）在创建Pydev项目时，会出现下面的选项（见图3-1），我们选什么呢？答案是选Python，那么Jython、IronPython是什么？简单地说，Python语言有Cpython、Jython、IronPython三种实现方式，我们用的是Cpython，即通常说的Python。Jython是为了与Java语言集成而实现的一种Python语言，IronPython是为了与Windows的.Net框架集成而实现的一种Python语言，可以把这三者的关系理解成：Python是普通话，Jython和IronPython是两种方言。

（4）创建好Project以后，在Eclipse最左边会看到一个“Pydev Package Explorer”（见图3-2），里面有我们所创建的所有Project，其中包含“lab1”，因为这个窗口上有一个“×”，有时候也会不小心把它整个关了，这时只要选择主菜单栏的“Window→Show→Pydev Package Explorer”就能把它恢复。

图3-1　Pydev项目类型

图3-2　Pydev包资源管理器

我们在test.py中输入三行代码如下，保存之后发现几个奇怪的地方（见图3-3）：

①import、print变成蓝色了。

②import os前面有一个惊叹号。

③c=os.getcwd（）前面出现了一个红叉。

④单击Run按钮，试图执行这个程序，则出现了下面的错误（见图3-4）。

图3-3　保存代码

图3-4　程序执行报错

解答1：import、print还有很多别的词在Python中都有特殊含义，叫保留字，编程时不能重新定义，只能使用，变成蓝色就是提醒开发人员，这是保留字，这样的字还有如下几十种（见表3-1）。

表3-1　Python的保留字

解答2和3：前两句话的惊叹号和红叉是相关联的，都是因为没有对齐的原因。在Python中，代码块中使用相同的缩进来标明一段代码块的开始和结尾（也就是它们会左对齐）。把它们左对齐以后，就没问题了。

解答4：单击Run按钮报错，这是因为我们还未对这个文件设置如何执行，解决办法有两种。办法1，选择Run as→Python Run菜单就可以执行，而且再单击Run按钮时就可以执行了，这是因为我们已经手动设置了应该选择哪种执行方式来执行这个程序。办法2，在Python Package explorer中选择要设置的文件，右击，在弹出的快捷菜单中，选择“properties”中的Run/Debug Settings选项，手动指定哪个项目中的哪个文件需要执行，并且命名以后，再单击apply and close，可以达成一样的目的。

解答5：这个程序在干什么？

3.4　Python的数据类型

Python有如表3-2所示的一些内置基本数据类型，可以定义也可以赋值。

表3-2　Python基本数据类型

后续章节会细讲这些内容，如果你现在看了有点懵，也不要紧，以后可以翻回来再查。

3.5　Python基本语句和控制语句

（1）Python程序由模块构成。

（2）模块包含语句。

（3）语句包含表达式。

（4）表达式建立并处理对象（上一节介绍的基本数据类型都是对象）。

模块我们前面已经接触过了，import os就是把os整个模块导入进来，简单来说，每一个以扩展名py结尾的Python源代码文件都是一个模块。其他的文件可以通过导入一个模块读取整个模块的内容。从更宏观的角度来看，模块扮演了一个工具库的角色，它往往就是变量名以及一些特定函数的封装，可以通过引入模块来使用这个模块里的变量和函数。可以用生活中一个具体的例子来类比一下，家中通常会购买螺丝刀、扳手、卷尺等日常工具，并们把它们收到一个工具箱中，当需要时，我们会把工具箱搬出来（就是import toolbox），如果需要刀就把刀拿出来用（在Python中用toolbox，knife来使用它），我们暂时讲到这个深度。后面有时候我们需要用到一些尚未安装在本机的模块，这时需要用到pip这个工具来安装模块。

表3-3列出了Python所有的语句形式，读者现在看可能会觉得莫名其妙，这个表可以先不管，用于以后待查。

表3-3　Python语句总结

3.6　简短实例

打开Eclipse，按3.3节的方法在lab1这个工程里创建一个新文件，命名为Test2.py，然后把上面代码输入到文件中，注意，每行的前面的数字不需要输入。

第1行，i=0

我们定义了一个变量i，把它的初始值设为0。

Python中变量定义的规则如下：

（1）语法：（下画线或字母）+（任意数目的字母、数字或下画线）。

（2）变量名只能是字母、数字或下画线的任意组合。

（3）区分大小写，比如ii=0，II=1，这是两个不同的变量。

（4）变量名的第一个字符不能是数字，比如3Com=0，这个变量定义是非法的。

（5）保留字不能声明为变量名。

     ['and','as','assert','break','class','continue','def','del','elif','else',
     'except','exec','finally','for','from','global','if','import','in','is','lambda',
     'not','or','pass','print','raise','return','try','while','with','yield']

第2行while True：

开始了一个循环体，while后面是一个循环测试条件，测试条件之后是冒号“：”，每执行循序体一次就要再测试一下条件是否还满足，这个循序后面跟的是一个常数True，即永远满足的条件，那么这个循序体如果不通过break跳出，就会是一个一直执行下去的死循环。第3～7行，全部以4个空格并左对齐，构成一个循环体，这就是前面提到过的对齐规则，在执行完第7行最后一句print之后就会回到第二行继续执行。

第3行reply=input（"Enter text："）

第3～7行构成循序体，第3行调用了系统的输入函数input（），它的函数参数是一个字符串"Enter text："，这个函数会在Console（命令行窗口）上把输入的字符串打印出来后，等待用户输入，一旦按回车键（Enter），输入结束，把输入的字符串赋给reply这个变量。之所以要有"Enter text："，就是为了提醒用户，如果不输入提示字符串，用户完全不知道计算机在等待用户的输入。

第4行if reply=="stop"：break

判断用户刚才输入的字符串是否等于"stop"，如果是就break，break就会立即跳出循环体而不再执行后面的语句。if是分支判断，reply=="stop"是判断条件，这种语句的句型通常是：

第5行print（reply.upper（））

打印reply这个字符串，在打印之前会调用字符串的函数upper（）把字符串转换成大写。注意这个优先级，括号（）里面的优先级最高，会首先执行。

第6行i=i+1

把i+1赋给i，每循环一次就增加1，统计退出前循环了多少次。

第7行print（"you have tried"，i，"times"）

打印一共循环了多少次。

这个程序里面出现了字符串，字符串的使用比较复杂，我们总结一下：

（1）可以用单引号、双引号（如"anything"、'anything'）定义字符串，二者等效。

（2）反斜杠进行转义，比如我们需要一个字符串'it's him'，字符串中本身出现了一个单引号和第一个单引号配对，造成字符串被截断了，这时可以通过在那个需要转义的单引号前加“\”，告诉Python解释器，那个单引号是字符串的一部分：'it\'s him'。

（3）三重引号做注释，三个双引号或单引号配对之间的所有内容都是注释，而且可以跨多行，比如下面的内容就全是注释，不会执行：

（4）字符串可以分片访问，其中的“1：3”，就是从第1个到第3个，不包括第3个，[：-1]就是到倒数第一个为止的意思。大家可以打开Python的命令行，仿照看看下面的实例，自己做一下实验。

（5）字符串还有很多方法可以调用（见表3-4），比如如果我们已经定义了T="hello"。

表3-4　方法调用及返回结果

还有很多其他的函数，可以在Python的Console里通过输入help（str）获得关于字符串的全部文档。也可以通过搜索引擎获得字符串的详细解释。后面我们用到的时候再讲解一些知识点。

从下一章开始我们逐步进行一些简单编程。

4　莱布尼兹级数

【本章摘要】

数学知识：莱布尼兹级数的通项公式，无穷级数求和的公式表示法。

编程知识：for和while循环语句的特点，Python加、减、乘、除的写法，赋值的方法，运算符的优先级。

莱布尼兹（1646—1716）是德国哲学家、数学家、职业律师，历史上少见的通才，被誉为17世纪的亚里士多德。在数学上，他和牛顿先后独立发现了微积分，1684年，他首先公开发表了第一篇微分论文，定义了微分概念，采用了微分符号dx、dy；1686年，他又首先公开发表了积分论文，讨论了微分与积分，使用了积分符号∫。这些数学符号今天我们仍在使用。此外，莱布尼兹还发明并完善了二进制，这是今天计算机科学的数学基础。在研究微积分的过程中他首先发现了一个非常简洁的求圆周率的公式：

首先我们来认识一下这个通项公式：分母全是奇数，可以用2n-1表示，从n=1开始取值，分子为1，每一项的符号则是正负符号交错，第一项是正的，第二项为负，那么可以写成（-1）n⁺¹，n=1时为正，上面这个公式也可以写成如下形式：

其中，∑是一个求和符号，读音是Sigma，符号底下的n=1表示从1开始，上面的∞表示一直加下去，直至无穷。用这个求和符号后面接通项公式，就表示了一个无穷级数的求和。注意，我们在学习有理数的性质时，有“两个有理数的和仍是有理数”的结论，这个级数反映了这样一个事实，即无穷多个有理数的和可以是无理数。下面我们写一个Python程序来验证上面这个公式是否正确。

我们用两种方法来验证。

方法1：我们连续地加10 000项，看看和是多少，这个方法可以用for循环语句来实现。

代码如下：

我们初始定义了两个变量sum为0，sign为-1，sum是为了存放累加的和，sign就是为了计算（-1）n⁺¹，初始为-1，以后每次再乘以一个-1，就计算出了这个（-1）n⁺¹的值。代码中我们也看到乘法用“*”来表示，除法用“/”来表示，为了让2*n-1作为一个整体来计算，我们用“（）”把它括起来。

程序中用了一个for来控制循环多少次，句型为“for（控制变量）in（有序序列）：”，这里面包括冒号“：”在内一共5个元素，一个都不能少；Python中range（）函数可以创建一个整数列表，一般用在for循环中。

range（start，stop[，step]）的参数说明：

start——计数从start开始，默认是从0开始。

stop——计数到stop结束，但不包括stop。

step——步长，默认为1，如果为1，这个参数可以不填。

range（1，10）实际上将生成[1，2，3，4，5，6，7，8，9]这个列表，n就依次从1一直变到9，每次增加1。程序中range（1，10001），所以程序中就从1开始一直循环变到10000结束。

程序中我们用sum+=sign/（2*n-1）实现累加，sum+=是sum=sum+…的一种简写法，这句话表示循序累加。这个程序累计执行10000次后，结果确实与π很接近了。

假如我们已知π=3.141 592 6，希望验证这个公式要加到n为多少时才能与已知的π之间的误差小于0.000 01，可以用while这种循环语句的方式来验证，while循环语句的句法为“while（循环继续的条件）：”，包含3个元素。

代码如下：

这次我们用到了while这种循环方法，这个程序中有两个新的知识点。

（1）我们用import导入了math这个模块，并且在程序中用math.pi来引用这个模块中定义的π常数，大家可以在Python的Console中尝试一下，它的值是3.141 592 653 589 793。

（2）我们调用了Python中内置的函数abs（），这个函数就是求绝对值，程序中判断是否继续循环的条件就是判断我们求出来的级数和的4倍与π的差是否比0.000 001更小，如果还没有就继续求和，直到满足要求。

我们发现，一共要循环100万次才达成目标。

总结：在明确知道要循环多少次的情况下，可以用for语句来构建循环；而不能预知要循环多少次的情况下，可以用while语句来构建循环。

练习

1．用for循环编程求1+2+3+…+2020的和。可以首先讲解等差数列的求和方法，再编程验证。

2．分别用for循环语句和while循环语句编程求1+3+5+7+…+2021的和，注意在讲解时，要小朋友们先找到通项公式，然后用通项公式求出应该循环多少次。

5　斐波那契数列和一个小魔术

【本章摘要】

数学内容：斐波那契数列及其通项公式，斐波那契数列的前后两项之比为黄金分割比。

编程内容：for循环、while循环及列表的各种操作，if/else，条件判断。

5.1　斐波那契数列

列昂纳多·斐波那契（Leonardo Fibonacci，1170—1250），意大利数学家，他在研究兔子繁殖问题时，发现了斐波那契数列。一般而言，兔子在出生两个月后就有繁殖能力，一对兔子每个月能生出一对小兔子。如果所有兔子都不死，那么一年以后可以繁殖多少对兔子？

我们不妨从一对新出生的小兔子开始分析一下：

第一月，兔子刚出生，只有一对兔子；

第二月，小兔子还没有繁殖能力，所以还是一对；

第三月，生下一对小兔，共有两对兔子；

第四月，老兔子又生下一对，因为小兔子还没有繁殖能力，所以一共是三对。这样继续画下去我们可以得到图5-1。

图5-1　兔子繁殖示意图

我们把月份、成年兔子对数和幼仔对数放入一张表，依此类推可以列出表5-1。

表5-1　兔子繁殖表

幼仔对数=前月成兔对数

成兔对数=前月成兔对数+前月幼仔对数

总体对数=本月成兔对数+本月幼仔对数

可以看出幼仔对数、成兔对数、总体对数都构成了一个数列。这个数列有个十分明显的特点：前面相邻两项之和构成了后一项。这样我们把兔子对数写下来构成斐波那契数列：

1，1，2，3，5，8，13，21，34，55，89，144，…

如果设an为该数列的第n项（n∈N*），那么相邻项的关系可以写成如下形式：

an=an-₁+an-₂

此时，

a₁=1，a₂=1　an=an-1+an-2　（n≥3，n∈N*）

有了这个公式，我们用程序来产生斐波那契数列的前100项，并把它存在一张表里面，程序如下：

这个程序里面使用了列表这种数据结构，列表是Python中一个重要的数据对象，可以把它看成一个一排编了号的抽屉，抽屉中能存放任何能放得下的东西，可以通过编号访问，可以排序，可以增加。表5-2列出了常用的操作中列表的方法，表中假设已经定义了一个列表L。

表5-2　常用的列表操作方法

人们发现，当n趋向于无穷大时，前一项与后一项的比值越来越逼近黄金分割0.618，我们可以试着计算一下：

1÷1=1，

1÷2=0.5，

2÷3=0.666…，

3÷5=0.6，

5÷8=0.625，

……

55÷89=0.617 977…，

144÷233=0.618 025…，

……

46 368÷75 025=0.618 033 988 6…，

……

可以看到，从第11个数开始，已经与黄金分割比的前三位小数一致了。仍然用前面的小程序来核算一下：

5.2　猜数魔术

利用上面发现的斐波那契相邻两数之比是黄金分割比这个性质，我们可以设计一个魔术：先制作一个空的表格（见图5-2），表格内不填任何数，发给小伙伴们，在确保你不看到他们填的任何数字的情况下，要小伙伴们在前两个空格内随便填两个小于10的数，第一个比第二个小，然后从第三个数开始，后面的每一个数都是前面两数的和。让小伙伴按规矩填好10个数以上后，表演就可以开始了，你可以自信地说，随便他们报第10个数以后的任何数字，不用看他的表格，你就知道他的下一个数字是什么，这个魔术可以把你的小伙伴们唬得一愣一愣的，当然你我都知道这不过是斐波那契数列的一个性质而已，对高手来说，他一眼就知道这个道理，这个魔术中的斐波那契数列的头两项不是1、1，而是扩展到了任意正整数，这个黄金分割比率关系仍不变。

图5-2　猜数魔术

6　欧几里得游戏和最大公约数的求法

【本章摘要】

数学内容：求最大公约数的两个算法（辗转相减法和辗转相除法），博弈论简单启蒙。

编程内容：函数，if/else，条件判断，递归函数。

科尔（Cole）和戴维（Davie）是好朋友，两人都喜欢同班的一个女同学，为了不影响两人的友谊，他们决定玩一个称为欧几里得的游戏，谁输谁退出。玩法是这样的：游戏开始时，有两堆棋子，分别有a和b颗棋子（假定a>b），两人轮流从较多一堆棋子中按规定取子，规定每次取子数目必须是较少的一堆棋子数量的正整数倍，取走的棋子数不限，只要数量够就可以取。这样轮流取子以后，率先将任何一堆棋子取光的人获胜。于是他们两人各抓了一把棋子，一堆33个，一堆7个，他们又通过抛硬币的方式决定科尔首先取子。表6-1记录了这次游戏过程。

科尔痛不欲生，但愿赌服输，他回家以后好好研究了一下这个数学游戏，发现了诀窍，信心满满地知道自己下次不会再输了。我们等会儿再揭秘一下他的发现，现在我们先来学习一下这个游戏中的数学知识，为解密做准备。

表6-1　欧几里得游戏过程

最大公约数也称最大公因数，指两个或多个整数共有约数中最大的一个，比如35和21的最大公约数是7。a，b的最大公约数记为（a，b），a，b，c的最大公约数记为（a，b，c），多个整数的最大公约数也有同样的记号。

求最大公约数有多种方法，常见的有质因数分解法、辗转相除法、辗转相减法（也叫更相减损法，后面这个名字出自《九章算术》，约成书于公元1世纪，作者不详，西汉时期张苍、耿寿昌对这本书曾经做过增补和整理，是中国最早的一部数学专著）。这里主要介绍用计算机程序求两个数的最大公约数（Greatest Common Divisor）的方法。

6.1　辗转相减法

辗转相减法，也叫更相减损术，是出自《九章算术》中的一种求最大公约数的算法，它原本是为约分而设计的，但它适用于任何需要求最大公约数的场合。《九章算术》中记载：“可半者半之，不可半者，副置分母、子之数，以少减多，更相减损，求其等也，以等数约之。”

翻译成现代语言如下：

第一步：任意给定两个正整数，判断它们是否都是偶数。若是，则用2约简；若不是，则执行第二步。

第二步：用较大的数减较小的数，接着把所得的差与较小的数比较，并以大数减小数。继续这个操作，直到所得的减数和差相等为止。

则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。

我们看看下面的一个实例：

例1　用更相减损术求260和104的最大公约数。

解：由于260和104均为偶数，首先用2约简得到130和52，再用2约简得到65和26。

此时65是奇数而26不是奇数，故把65和26辗转相减：

65-26=39

39-26=13

26-13=13

所以，260与104的最大公约数等于13乘以第一步中约掉的两个2，即13×2×2=52。

这个过程可以简单地写为

（260，104）（/2/2）=>（65，26）=（39，26）=（13，26）=（13，13）=13.13×4=52

辗转相减法即通过对两数的不断减法运算来代替除法运算。

例如：两个自然数35和14，用大数减去小数，（35，14）=（21，14）=（7，14），此时，7小于14，要做一次交换，把14作为被减数，即（14，7）=（7，7），再做一次相减，结果为0，这样也就求出了最大公约数7。下面的代码和更相减损术的代码略有差异，省掉了以2取的过程。但本质是一样的。

Python代码如下：

这个代码中，出现了两个新的语法现象：

（1）“if（a==b）：”，这是Python中的条件判断语句，其语法是：

条件判断，可以是一切会产生True或False的运算，典型的如表6-2所示。

表6-2　条件判断语句中的运算符

（2）“def gcd2（a，b）：”，这是Python语言中函数的定义，其语法是：

def告诉Python解释器，我们定义了一个函数，它的名字就是后面跟的函数名name，这个函数有0个或以上的参数，函数内的代码块需要缩进，代码块中往往会包含一条return语句，return语句可以出现在函数体中的任何地方，它表示函数调用的结束，并将结果返回到函数调用处；return语句也不是必需的，如果它没有出现，那么函数会在执行完最后一句语句时结束。

什么情况下会需要定义一个函数呢？

函数主要有两个好处，定义好以后，可以在需要的地方反复使用，因此：

（1）最大化代码重用。

（2）把某个独立的过程用函数封装起来后，使得代码简洁，利于维护。

Python函数还有一个不同于其他语言定义的函数特征：在函数定义时，并没有规定输入和输出参数是什么类型，可以是任何参数。比如：

     def addTwoElements（a,b）:
     returna+b

输入参数a，b没有规定一定是数字还是字符串，或是其他的对象，所以这个函数的功能会随着输入参数的类型变化而行为有差别，如果是addTwoElements（3，5），则返回结果是8；而如果是addTwoElements（“hello”，“world”），则返回的是“helloworld”，这种随输入类型不同而不同的行为叫多态（是否像变色龙？），即多种形态的意思。

大家是否注意到上面这个函数还有一个不同寻常的地方，就是gcd2（）这个函数自己又调用了自己。这种行为是合法的，而且有个专门术语叫“递归函数”，我们举一个生活中类似的例子来理解这种函数行为，以及这种函数特有的一些约束。如果手表坏了，我们打算自己动手拆开进行维修，为了保证拆了以后能够又完整地拼装起来，我们可能会拆到某个部件时，把它和周围的部件应该如何拼接的步骤用照片或本子记下来，然后单独拿到一个新的区域去另外拆分，零件各放在独立的区域，继续拆，碰到又一个部件时，继续同样的步骤，部件和部件之间拆下来的零件不会交叉，以免混在一起。这样一直拆，拆到我们的工作区域都占满了以后，我们就会停下来，因为再拆下去，零件就会混杂在一起，再也装不回去了。递归函数也有类似的限制，每一次递归，Python都要保存一下当前的现场，即函数内变量的状态，将来函数调用返回时，我们可以继续在上次调用的地方返回去，所以不能无限制地递归下去，递归到一定程度，如果还没完成计算，就会出错，拒绝继续递归。

6.2　辗转相除法

辗转相除法也叫欧几里得算法，是一种非常古老的求解两个数的最大公约数的算法。其基本的原理：两个正整数a和b（设a>b），它们的最大公约数（英文简写为gcd）等于a除以b的余数r和b之间的最大公约数。我们举个具体的例子来看一下。

例子：求（319，377），注意下面算式中的“÷”号为整除的意思。

因为319÷377=0（余319）

所以（319，377）=（377，319）；

因为377÷319=1（余58）

所以（377，319）=（319，58）；

因为319÷58=5（余29）

所以（319，58）=（58，29）；

因为58÷29=2（余0）

所以（58，29）=29；

所以（319，377）=29。

根据上面的原理，辗转相除法的算法流程可以如下：

步骤1：计算a与b的余数r。

步骤2：如果r为0，则返回gcd（a，b）=b，否则，转到步骤3。

步骤3：使用b的值更新a的值，使用余数r更新b的值，转到步骤1。

代码如下：

等等，程序中为什么不对a和b的大小进行判断呢？上面的算法原理中不是要求a大于b吗？如果调用时a值大于b值，比如a为51，b为21，那么情况跟上述算法原理是相符的。如果调用时a值小于b值，比如a为21，b为51，那么，21除以51的余数r为21，不为0，于是接着调用gcd（51，21），发现了吗？这就和a>b的情况一样了。也就是说我们根本无须判断a和b的大小，当a值小于b值时，算法的下一次调用就能够将a和b的值交换过来。

“a%b”是什么运算？a%b就是a整除b以后的余数，比如5%3为2，3%5为3，8%4为0，4%8为4。

以上两种算法都可以计算任意两个数的最大公约数。在什么条件下，哪一种算法的速度更快，大家可以用如下的代码来验证一下，然后自己分析结果。

注意：这个代码中用到了time.perf_counter（）函数，这个函数的功能就相当于高精度计时器的启／停开关，并把时间读数返回，显然我们需要在代码的最初import time。

学习了求最大公约数的方法以后，我们发现，欧几里得游戏的过程恰好是用辗转相减法求最大公约数的过程。回到前面的故事，科尔研究出的这个游戏的必杀技是怎样的呢？

科尔研究的结论有以下3点：

（1）这个游戏不是碰运气，胜负中含有数学规律。

（2）两个棋子的个数确定以后，必须仔细分析最后决定胜负的那一次选择会在什么条件下出现，一定要想法把最后控制胜负的那一次选择权掌握在自己手中，否则必输。

（3）推演出决定胜负的那一个局面之后的两条路线，选择必胜的那条路线。

我们以科尔输的例子（33，7）来说明，因为33=4×7+5，所以无论怎么取子，最后一定会出现（5，7）这个局面，用前面学过的最大公约数的符号来描述就是：（33，7）=（5，7），出现（5，7）这个局面时，接下去要取子的这个人没有选择，他只能从7中取5，那么就会出现（5，2）这个局面，用数学符号描述就是：（33，7）=（5，7）=（5，2），出现（5，2）这个局面以后，本轮掌握选择权的人，就有两种选择：

（1）取2个子，剩下（3，2），接下去的局面就是对手只能取2个子，剩下（1，2），自己最终获胜。

（2）取4个子，接下去的局面就是（1，2），则对手胜。

很显然，谁掌握最后的（5，2）这个局面，谁就具有选择权。因此再往前推演，就是如何取子可以迫使对手把（5，2）这个局面剩下来留给自己。

掌握了这个诀窍以后，我们来尝试一下，（64，5）如何取子可以导向胜利呢？

容易知道（64，5）=（4，5）=（4，1），谁被迫选择（4，5）谁就输，那么取子时，就要想办法迫使对手选择（4，5）。怎么取子你会了吗？

作业

1．用程序实现从键盘接收用户输入的一个数字n，并以这个数字为起点，构造一个等差数列，如果输入数字n是奇数，等差数列之间的差为3；如果n是偶数，则等差数列之间的差是4。从n开始，连续加100项，求这100项之和。

2．用递归函数求20的阶乘，注意讲解阶乘的数学意义。

3．巴什游戏，一堆石子有n个，两人轮流取子，规定每人每次至少取一个，最多取m个（m<n），最后取完石子的人获胜，请问应该如何取子？

7　幸福的数

【本章摘要】

数学内容：数的乘法和加法，集合的概念。

编程内容：递归函数、集合set的使用。

注意事项：本章进一步实际操练递归函数，进一步举例递归过程中可能出现的死循环和防止办法，递归函数部分需要至少实际练习4个实例。

本章我们来玩一个简单的数字游戏。

把任意一个数n拆分成单个数字，比如23，拆成2，3两个数字，然后全部平方并相加，得到一个新数m，本例中，

m=2×2+3×3=13

如果这个数字m=1，那么n是忧伤的，否则，继续进行拆分，然后求平方和。本例中就是把13继续拆分为：1×1+3×3=10，1×1+0×0=1，最终23经过三次这样的运算，成了1，因此23是一个忧伤的数，因为它经历过很多事以后，孤独终老，怎能不忧伤呢？

这个游戏如果用Python来实现，找出在1000以内有哪些数是忧伤的，哪些是幸福的，该怎么实现呢？

分析：每个数都要进行拆分，求平方和，判断是否是幸福的数，如果不是则继续同样的过程，这个行为非常适合用递归函数来实现。在计算过程中，会碰到一些以前已经算过的数，并且已经知道它是忧伤或幸福的数，就没有必要继续算下去了。这个信息要存储起来，而且计算前要不断地比较，那就涉及判断一个数是否在某个集合中，这个数据结构使用集合set来存储比较合适。注意，在分析过程中，注重程序的流程和用到的数据结构。

结论1：用递归函数来实现。

结论2：用set来存储已经确定是忧伤／幸福的数。

7.1　Python的对象类型之集合

Python中有一种数据类型叫集合（Set），这是一些唯一的、不可变的对象的一个无序集合，这些对象支持与数学集合理论相对应的操作，比如求交集、并集。在Python的Console中，我们可以实验如下的集合操作。

集合上还有一些函数操作，比如add（）、remove（）、update（）、intersection（），这些函数的含义如表7-1所示。

表7-1　函数及含义

7.2　实验验证

代码如下：

这个程序中，有四个新的知识点要说明一下：

（1）i**2，两个乘号连用，表示对前面的数求平方。

（2）str（n），这个函数把参数n转换成字符串，比如输入23这个数字，就转换成了字符'2'和字符'3'，这两个有什么差别，数字就是2，3，不需要加引号，字符就需要加引号。进行这个转换后，23就成了一个字符序列，所以再求平方的时候，又用int（i）把它转换成数字。

（3）在定义sadnumber（n，past=set（））时，传入了past=set（）这个奇怪的参数。其实这个参数不奇怪，它的意思是第二个参数可以不传，不传的情况下，它就默认传入了一个空的集合。

（4）sum（）是系统的求和函数，但是这个函数的参数是通过一个复杂的语句生成的：“int（i）**2 for i in str（n）”，这种方法叫作生成器表达式，这个语句等价于（int（i）**2 for i in str（n）），它生成了一个迭代器，与列表解析式有微妙的差异，这个特性是Python语言中比较高级的特性，读者可以暂时放在一边，理解其含义就可以。还是用Python的Console执行一下看看结果：

代码的含义就是把每个数字的平方和进行累加。

程序执行完以后，我们看看20以内的忧伤的数有1，7，10，13，19，是不是与我们的常识差不多？西方人不喜欢的13也在里面，而我们常常喜欢选的2，6，8都是幸福的数，下次再看到这些数，是不是多了一些感觉？

作业：编程求阶乘，并把阶乘的结果放进一个表格，打印出来。

8　考拉兹猜想的实验验证和图形化

【本章摘要】

数学内容：奇数和偶数，考拉兹猜想。

编程内容：for循环语句，while循环语句，if-else语句，函数，列表，字典，matplotlib的使用。

德国数学家考拉兹（Lothar Collatz）在1937年首先提出了这么一个猜想：对任意正整数x，如果是奇数就乘以3再加1，如果是偶数就除以2，对得到的结果再重复上述过程，经过若干次这样的运算，任何正整数最终都会回到1，这个猜想因此被命名为考拉兹猜想（Collatz Conjecture，也叫角谷猜想、奇偶归一猜想）。比如，以13为起点，那么13×3+1=40，40/2=20，20/2=10，10/2=5，5×3+1=16，16/2=8，8/2=4，4/2=2，2/2=1，回到了1，猜想成立。有人通过计算机验证到非常庞大的数字，就目前结果来看还没发现任何反例。但至今也没有人能够证明这个猜想。据说澳大利亚华人数学家陶哲轩（1975-）无限接近证明了这个猜想⁽¹⁾。大家会奇怪我们使用了“无限接近”这个词来表达这个事实，因为他的证明比较特殊，也使用了无限接近这种手法，好奇的同学可以去查看文献获取更多的信息。

8.1　考拉兹猜想的实验验证

现在我们来编程验证这个猜想，验证程序需要完成三个任务：

（1）验证这个猜想在10000以内是否都成立。

（2）记录每一个数各经过多少次运算才回到1，并在二维图上打一个点，水平轴表示数本身，垂直轴表示运算次数，比如前面我们验证过的13，经过9次运算回到1，那么就在（13，9）这个位置标一个点。

（3）如果某个数经过若干次运算以后，又回到它自己，那么会构成一个循环，一直不会结束，程序需要防止出现这种情况。

分析：

每次核算一次一个数是否能归一，可能需要多次运算，而且每计算一次，计数器还要加1，最终返回总运算次数。可以用一个函数来完成这两项工作，这个函数接收一个输入，返回是否归一以及计算次数。

计算结果可以存入一个列表里面，列表的索引就是这个数，列表里存储运算次数。

可以用模块matplotlib来进行绘图，第9章专门讲述matplotlib。

代码如下：

这个程序有几处新的Python知识点：

（1）import…as…句法，该语句的意思就是使用as之后的变量名来引用这个模块。其好处是在程序中对模块的引用变得简洁，在程序中本来应该使用matplotlib.pyplot这个变量名，现在全部简写为plt。

（2）第24行，我们的函数调用方式是testResult，times=collatz（i），collatz（）返回两个变量，因此赋值表达式的左边有两个变量。

（3）第22行，定义了一个空的列表，第26行、28行通过列表函数append（）往列表尾巴上添加一个新的元素。

（4）第30～33行用matplotlib画图（见图8-1），这个模块的内容很丰富，下一章专门讲如何用matplotlib来画图。本例中，fig=plt.figure（1）创建一个图，此时图中还没有任何东西。

ax=fig.add_subplot（1，1，1），在这个图上只画一个坐标平面ax，ax.scatter（range（1，10 000），L，color='r'，marker='.'），在ax这个坐标平面上画一个散点图，range（1，10 000），说明X轴从1一直到10 000，每次增加1，参数L中存放从1到10 000的每个数字经过多少次运算才归一。color='r'指定散点颜色是红色，marker='.'指定点的形状就是个圆点。

图8-1　图形化结果

图8-1的横坐标表示1～10 000的每一个数字，纵坐标是X轴对应的数字经过多少次运算后才归一，图形上看明显展示出了鱼鳞状的规律性。

8.2　考拉兹猜想的树形图

图8-2显示的是我们在讨论考拉兹猜想的书籍或论文中常常看到的树形图。每个数字在分支中以从上往下的方向显示考拉兹序列中的数字，例如之前列举的数字13，顺着13往下便能找到根据考拉兹规则计算出的那一系列数字。迄今为止调查到的所有数字都会到达4、2，最后再到1，因此所有数字都是起源于同一树中的分支。美国阿肯色大学的埃德蒙·哈里斯（Edmund Harriss）教授又提出了另外一种图形化的方法，他在标准图的基础上新添了一条关于分支方向的规则，用来反映一个数字是由两种算法中的哪一种运算而生成，即：无论你是树枝中的哪个数字，如果在你上面的数字是你的2倍，则上端分支会以固定角度向顺时针方向转动；如果不是2倍，则以固定角度向逆时针方向转动。这样得到如图8-3所示的形状。如果把数字去掉，并对线条着色，我们就可以得到一个像是正在野蛮生长的水藻或是正在绽放的礼炮的动画，它的实现也不太复杂，我们来动手实现一下。

图8-2　考拉兹树形图

图8-3　第二种考拉兹树形图

要实现图8-3中第二种考拉兹树形图首先要找到树形关系图，我们在玩游戏时是正推，比如3→10→5→16→8→4→2→1，但是现在需要倒推，也就是说，我们要找什么数会走到1，很显然只有2，什么数能走到2，只有4，如此往上逆推，当走到16时，情况发生了变化，32→16，5→16，也就是说16有{32，5}两个父节点，然后，我们要继续为32和5找父节点。很明显，一个永远存在的父节点就是自身的2倍；另一个则可以通过化简正向推演的公式求出，当前数是奇数时，我们就乘3加1，写成数学表达式就是

因为

k=3n+1

所以

n=（k-1）/3

执行这个运算的前提是k-1能被3整除，另外还要保证求出来的n一定要是奇数，否则根本就不是执行乘3加1这个操作。怎么保证这两个条件呢，我们把n改写成2m+1，代入到k=3n+1，可以得到

因为

k=3（2m+1）+1=6m+4

所以

（k-4）mod 6=0

也就是说k-4要能被6整除，才需要计算n=（k-1）/3整个操作。

通过几次简单手算以后，很快发现，每往上推演一层，就会有多个节点，每个节点又会延伸出最多两个父节点（有些只有一个父节点，比如6），整个结构就是一个典型的树状结构，这种数据结构怎么存储比较方便呢？我们下面介绍Python的另一种数据结构——字典。

8.3　Python的对象类型之字典

字典和列表的相同之处是二者都可以拿来存放别的对象，可以看成一种容器，但是在访问方式上完全不同。列表可以通过位置来访问，比如L[2]，甚至序号可以用公式、相对偏移量等方式来计算；字典则不同，它是一种映射关系，通过“键”来访问。字典的使用方式是这样的：

字典这种容器的命名不是很贴切，因为对于生活中的字典，固然是拿字母来进行索引，对应的就是实例中的键值，如'name'、'job'等，但是我们知道真实字典中存放的永远都是单词，一个更贴切的例子应该是快递点的仓库。快递包裹多种多样，我们不知道包裹里面究竟装的是什么，每收到一个包裹，我们会贴上一个可以索引的标签，类比于实例中的“键”，以后我们会根据整个标签去查询整个包裹，查出来的是一个对象。

可以通过给新的键赋值来扩展字典，比如：

通过实例可以看到，我们可以新添加键值，也可以测试是否存在某个键值。

8.4　数据结构分析

了解字典的数据结构之后，我们回到对考拉兹树形图描述的数据结构上来，树形的数据结构可以分两部分来描述，每向上倒推一层，获得所有的节点，可以用一个嵌套列表来存储，比如：

L[1]=[1]

L[2]=[2]

L[3]=[4]

L[4]=[8]

L[5]=[16]

L[6]=[5，32]

L[7]=[10，64]

L[8]=[3，20，21，128]

……

每个节点的父节点可以用字典来描述，比如：

D={}

D[10]={3，20}

D[64]={21，128}

……

8.5　考拉兹猜想的图形化

代码分成两部分。第一部分，专注上面一节中提到的数据的生成。

第二部分，学习完Turtle画图以后，读者可以自己尝试。

(1)　详细请见文献[3]。

9　matplotlib介绍

【本章摘要】

数学内容：无。

编程内容：matplotlib的安装，画散点图、柱状图、折线图、饼图的方法，线的控制。

matplotlib是Python的一个绘图工具包，官网https://matplotlib.org/包含了各种绘图的实例，这里只讲述本书会用到的一些知识，读者可以从官网上获得更详细的介绍。

9.1　matplotlib安装

pip是一个安装和管理Python包的工具，可以在命令行中用pip install xxx来安装我们想要的任何一个包，因此可以用pip install matplotlib来安装matplotlib。但是这里读者会碰到一个问题，由于matplotlib的官网服务器在国外，直接这样安装，包的下载时间会很长，这种情况下，我们可以使用国内的镜像源进行安装，国内的镜像源有：

（1）清华：https://pypi.tuna.tsinghua.edu.cn/simple。

（2）阿里云：http://mirrors.aliyun.com/pypi/simple/。

（3）中国科技大学：https://pypi.mirrors.ustc.edu.cn/simple/。

（4）华中理工大学：http://pypi.hustunique.com/。

（5）山东理工大学：http://pypi.sdutlinux.org/。

（6）豆瓣：http://pypi.douban.com/simple/。

对应的命令行为

     "pip install -i http://pypi.douban.com/simple/matplotlib"

9.2　matplotlib例程

matplotlib画图基本上要遵循如下几个步骤：

（1）导入matplotlib的组件。

（2）创建图和图上的各种元素，比如坐标轴和名称。

（3）配置图形元素的参数。

（4）构建图上要展示的数据元素。

（5）展示图形。

下面的代码完整地展示了这个过程，并用三个坐标轴画了柱状图、散点图和折线图三种常用的曲线图，执行结果如图9-1所示。

     import matplotlib.pyplot as plt
     data={'apple':10,'orange':15,'lemon':5,'lime':20}
     names=list(data.keys())
     values=list(data.values())

     fig,axs=plt.subplots(1,3,figsize=(9,3),sharey=True)
     axs[0].bar(names,values)
     axs[1].scatter(names,values)
     axs[2].plot(names,values)
     fig.suptitle('Categorical Plotting')
     plt.show()

图9-1　matplotlib图例

代码说明：

（1）data、names、values都是在构建数据，分别是字典类型、列表类型。

     fig,ax=plt.subplots（1,3,1）

第三个参数1是代表第一个子图。

（2）使用fig，axs=plt.subplot（1，3）函数调用来创建图和图上的坐标轴，第一个参数1是子图的行数，第二个参数3是子图的列数，因此在一个图像上生成了一行三列三个坐标区域，分别赋给fig，axs，三个坐标区域可以用ax[0]，ax[1]，ax[2]访问；figsize用来设置图形的大小，figsize=（a，b）。如果要设置子图的宽度和高度，a为图形的宽，b为图形的高，单位为英寸；其中sharey是share+y的合成，因为设置成True，即三个图在y轴的尺度一致，在不设置情况下，默认设为False，也可以用sharex=True来对应设置x轴的尺度。

（3）bar（）、scatter（）、plot（）分别是柱状图、散点图和折线图，但在数据要求上是一致的，第一项是x方向的一串数字，第二项是y方向的一串数字，二者要一一对应。

（4）fig.subtitle（）设置这个图的标题。

（5）调用plt.show（）函数后才会显示。

我们再来看一个用matplotlib画饼图的实例，如图9-2所示，其代码如下：

图9-2　用matplotlib画饼图的实例

饼图代码中出现了几个新的知识点，解释如下：

（1）第一个知识点，subplot（）函数调用中出现了一个新的参数subplot_kw=dict（aspect="equal"），这个参数用于控制每个子图的显示模式，它接受字典这种数据结构的对象，我们前面学过字典是一个“键”，带一个对应的“值”，本例中的键是"aspect"，值是"equal"，都是字符串类型，字典参数用于控制子图的宽高比，本例中设为相等，就意味着x方向和y方向的尺度一样，这样画出来的圆就是一个圆而不会是一个椭圆。

（2）第二个知识点，data=[float（x.split（）[0]）for x in recipe]是一个列表解析式，它的作用就是用循环语句生成一个列表。recipe这个变量在前面已经定义了，是一个字符串列表，for x in recipe就是自动地在recipe列表中循环，x的第一次取值是"375 g flour"，x的第二次取值是"75 g sugar"，…，x.split（）这个语句把循环中的x变量进行split，split的中文是分拆，在未给定用什么分隔符号来分拆的情况下，会默认采用空格或“，”。那么"375 g flour"字符串就被用空格分割成"375"、"g"、"flour"三个字符串，放到一个列表中，其中第[0]个是"375"字符串，再用float（）函数转成一个小数，就得到375.0。对所有的字串循序执行上面的操作，就分别得到制作饼的成分的量，执行完后data=[375，75，200，300]，同理会得到ingredients=["flour"，"sugar"，"butter"，"berries"]，data中存放的是制作做饼的成分的分量，ingredients中存放的是材料的种类，分别是面粉、糖、黄油、果酱。这个语句比较精简，请细细体会。

（3）第三个知识点，autopct="%3.1f%%"，这个格式化字符串用于控制饼图上如何显示各种成分所占的百分比，其中%是一个特殊字符，告诉Python后面要跟一个格式控制的字串，3.1f就是一个整数部分保留3位数，小数点后保留一位小数的浮点数。%%中第一个%是告诉Python后面有一个特殊字符，第二个%就是要原样输出一个%，因此图9-3中的饼图上就有37.5%。autopct这个参数期望用户给出一个格式化的字串就够了，它会自己利用前面给出的各个分量计算每一部分的百分比。这个格式化的输出略有遗憾的是只给出了百分比，而没有给出各种成分的绝对数量，如果能一起输出在饼图上就好了，实际上本例已经考虑到了这一点，请看下一个知识点。

图9-3　制作比萨饼的成分

（4）第四个知识点，在图9-2中，在第22行前面添加一个#号，把第21行的#去掉，使用语句autopct=lambda pct：func（pct，data），就可以把百分比和成分的绝对数量一起显示，我们怎么做到的呢？画饼图的函数pie（）的参数autopct用于设置饼图内的百分比的显示方式，通常期望的格式是format字符串或者format函数，format函数是一个匿名函数lambda，匿名函数是指一类无须定义函数名的函数或子程序。lambda函数可以接收任意多个参数（包括可选参数）并且返回单个表达式的值。其格式为：冒号左边是参数，可以有多个，用逗号隔开；冒号右边是表达式，这种函数只能有一个表达式。其实lambda返回值是一个函数的地址，也就是函数对象。在这里，“pct”不是我们传入的参数，即autopct是会自动计算的变量，我们传入的参数只有data，再结合第15行定义的函数func（），实际上我们就是把"{：.1f}%\n（{：d}g）".format（pct，absolute）这个字符串控制表达式传给了autopct参数，其中pct会自动计算，真实传入的参数data被用来计算absolute的值。

得到如图9-4所示的饼图。

图9-4　制作比萨饼的成分（百分比+绝对数量）

请注意图9-3和图9-4之间的差异。

9.3　线的样式控制

由于本书会大量使用plot函数去画线，因此总结一下画线时会用到的一些控制参数。

1．颜色

plot方法的关键字参数color（或c）用来设置线的颜色。可取值为：

（1）颜色名称或简写：b：blue、g：green、r：red、c：cyan、m：magenta、y：yellow、k：black、w：white。

（2）#rrggbb，三原色，用十六进制表示每个分量，如“#3344A5”。

（3）（r，g，b）或（r，g，b，a），其中，r，g，b，a取值均在[0，1]之间。

（4）[0，1]之间的浮点数的字符串形式，表示灰度值。0表示黑色，1表示白色。

2．样式

plot方法的关键字参数linestyle（或ls）用来设置线的样式。可取值为：'-'，solid；'--'，dashed；'-.'，dashdot；'：'，dotted；"，''，None。

3．粗细

设置plot方法的关键字参数linewidth（或lw）可以改变线的粗细，其值为浮点数。

举例如下：

绘图如图9-5所示。

4．marker

以下关键字参数可以用来设置marker的样式。

图9-5　设置线的粗细

·　marker

·　markeredgecolor或mec

·　markeredgewidth或mew

·　markerfacecolor或mfc

·　markerfacecoloralt或mfcalt

·　markersize或ms

其中marker可取值为：

·　'.'：point marker

·　'，'：pixel marker

·　'o'：circle marker

·　'v'：triangle_down marker

·　'^'：triangle_up marker

·　'<'：triangle_left marker

·　'>'：triangle_right marker

·　'1'：tri_down marker

·　'2'：tri_up marker

·　'3'：tri_left marker

·　'4'：tri_right marker

·　's'：square marker

·　'p'：pentagon marker

·　'*'：star marker

·　'h'：hexagon1 marker

·　'H'：hexagon2 marker

·　'+'：plus marker

·　'x'：x marker

·　'D'：diamond marker

·　'd'：thin_diamond marker

·　'|'：vline marker

·　'_'：hline marker

举例如下：

绘图如图9-6所示。

5．标签控制

plot方法还可以指定label，然后调用legend（）方法可以绘制标签。例如：legend（）方法可接受一个loc关键字参数来设定图例的位置，可取值为数字或字符串。0：'best'；1：'upper right'；2：'upper left'；3：'lower left'；4：'lower right'；5：'right'；6：'center left'；7：'center right'；8：'lower center'；9：'upper center'；10：'center'。

图9-6　设置marker的样式

绘图如图9-7所示。

图9-7　标签控制

10　如何证明圆面积公式和阿基米德的双重归谬法

【本章摘要】

数学内容：用双重归谬法证明圆面积公式。

编程内容：多边形的画法，事件循环，事件响应机制，回调函数。

大家都知道圆的面积公式是πr²，圆的周长公式是2πr，也知道π≈3.141 592 6…。小学在学习圆面积和周长公式时，教师通常不证明这两个公式，因为小学生还不具备证明圆的周长或面积公式需要用到的知识，当熟练掌握以后，没人再关注这两个公式是怎么推证出来的，今天我们要追随阿基米德的思路学习一下这两个公式的证明过程。

阿基米德（Archimedes，公元前287—前212），古希腊人，人类历史上最伟大的数学家之一。阿基米德是欧几里得的学生，用“名师出高徒”来描述这两者的关系再恰当不过了。有人说，如果找数学家们做一个调查，请他们列出人类历史上最伟大的10位数学家，可能不同的人会有不同的看法，但如果将人数减少到3人，那么这个名单会非常集中：阿基米德、牛顿、欧拉。

公元前约225年，阿基米德发表了一篇题为《圆的测定》的论文，论文由两部分组成：第一部分是圆面积与其周长之间的关系的论断及其证明；第二部分利用圆的外切与内接96边形，求得圆周率π的上界和下界，即22/7<π<223/71，这是数学史上最早明确指出误差限度的π值。在论文中他首次用“穷竭法”证明了圆与圆面积之比等于其直径平方之比。其中的核心思想是——圆可以被它的内接（或外切）多边形“穷竭”，“穷竭”的意义就是二者的面积之差可以小于任意给定的量。换句话说，圆的内接正n边形随着n的增大，其面积无限接近于圆的面积。同样的数学思想，中国魏晋时期的刘徽在公元260年左右的《九章算术注》中才首次提出。

这里有必要先介绍一下在阿基米德探讨这一问题时，有关圆面积问题的发展状况。当时的几何学家已经知道，不论圆的大小如何，圆的周长与直径之比总是一定的，即

式中，C代表周长，D代表直径，其比值就是我们常说的圆周率π。

欧几里得的《几何原本》证明了两个圆的面积之比等于两个圆直径的平方比，因此，圆面积与其直径的平方比是一个常数。用现代术语来说，欧几里得证明了常数k的存在，即因而

至此，一切顺利。但是，这两个常数之间有什么关系呢？也就是说，人们是否发现在“一维”常数π（表示圆周长与直径的关系）与“二维”常数k（表示面积与直径的关系）之间存在着一种简单的联系？显然，欧几里得没有发现这种联系。

此外在阿基米德之前，人们还发明了穷竭法，这个结论是这样的：任意给定一个已知圆，我们都可以作出它的内接正多边形或外切正多边形，其面积可任意接近圆的面积。这一过程非常直接，以内接正多边形为例，首先作内接正四边形，过圆心互相垂直的两条线和圆的交点就是内接正四边形的顶点；内接正四边形的每一条边再作中垂线，中垂线和圆的交点就构成正八边形的顶点，如此继续就可以得到8、16、32边形顶点，这个过程可以无限继续。显然，内接正多边形的面积永远不会等于圆的面积；不论内接正多边形产生多少条边，都永远小于圆的面积。但这个结论的关键在于，无论你要求正多边形的面积和圆的面积之间的差多么小，我们都能作出一个内接正多边形，而使圆面积与其内接正多边形的面积比这个给定的差值小，这一正多边形也许有几百条边或几千条边，但这并不重要，重要的是它存在。外切正多边形也具有类似的规律。只是外切正多边形面积会永远大于圆的面积，但也可以任意接近。正是结论中的这句“可任意接近”成为了阿基米德成功的关键。

下面有必要就阿基米德论证两个面积相等时所采用的逻辑方法作一个简单的介绍。在某种意义上，这种逻辑方法比我们以往所见到的任何方法都更复杂，或者说，至少更曲折。例如，我们可以回想一下，欧几里得是如何证明勾股定理的：他采取直接推证法，即从一些已知的定理出发，直接推证出勾股定理，这种证明方法属于正面论证。然而，阿基米德在论证更为复杂的圆面积问题时，采用了一种间接证明的方法。他认为，任何两个量A与B，一定只能属于下列三种情况中的一种：A<B，A>B，A=B。为了证明A=B，阿基米德首先假设A<B，并由此推导出逻辑矛盾，因而排除这种情况的可能性。然后，他再假设A>B，并再次推导出逻辑矛盾。排除了这两种可能性后，就只剩下了一种可能性，即A=B。这就是阿基米德极为精彩的间接证明方法——“双重归谬法”，将三种可能性中的两种引入逻辑矛盾。这种方法初看起来似乎有点绕圈子，但细想一下就会觉得非常合理。排除了三种可能性中的两种，就迫使人们得出结论，只有第三种可能性是正确的。依据这两个初步定理，我们来看看这位几何大师是如何证明《圆的测定》中的第一个命题的。

命题1　任何圆的面积都等于这样一个直角三角形的面积，该直角三角形的一条直角边等于圆的半径，另一条直角边等于圆的周长。

我们有必要理解一下为什么阿基米德会有这么奇怪的想法，构造一个三角形的面积来等于圆面积？实际上，在前面我们已经交代过了，圆的面积可以用外接正多边形无限接近，那么我们可以看看下面的几个图（图10-1、图10-2）。

图10-1　外接正多边形无限接近圆

图10-2　等腰三角形的等面积变形

圆的外接多边形把圆分割以后，连接多边形顶点和圆心，构成的一系列等腰三角形，所有三角形底边相同（外切多半形的边长），高相同（即圆的半径），因此把底边相加就构成多边形的周长。然后我们也知道，无论形状差异多大，同底同高的三角形面积相等，因此等腰三角形可以等面积变形并合成一个等效的大三角形，这么一看，阿基米德这么证明等面积其实是很自然的一个思路。

证明：阿基米德首先作两个图形（见图10-3）：圆的圆心为O，半径为r，周长为C；直角三角形的底边等于C，高等于r。我们用A代表圆的面积，用T代表三角形的面积，而前者就是阿基米德求证的对象。显然，命题宣称A=T。为证明这一点，阿基米德采用了双重归谬法证明，他需要考虑并排除其他两种可能性。

图10-3　证明1（作两个图形）

假设A>T：这一假设表明，圆面积以一定量大于三角形面积。换言之，其超出量A-T是一个正量。从前面描述过的穷竭法知道，通过作圆内接正方形，并反复平分正方形的边，就可以得到一个圆内接正多边形，其面积与圆面积不等，但其面积之差可以小于正量A-T。即A-圆内接正多边形的面积<A-T，化简得到

T<圆内接正多边形的面积

但是，这是一个圆内接正多边形（见图10-4）。因此，多边形的周长Q小于圆周长C，其边心距h当然也小于圆的半径r。我们据此得出

至此，阿基米德推导出了预期的矛盾，因为他已得出T<圆内接多边形面积，圆内接正多边形面积<T两种结论。这在逻辑上是不成立的，因此，我们得出结论，假设A>T不成立，圆面积不能大于这个特定三角形面积。之后，他考虑了第二种可能性。

假设A<T，这次，阿基米德假设圆的面积小于三角形面积，因而，T-A代表三角形面积对圆面积的超出量。根据穷竭法知道，我们也可以作一个圆外切正多边形，其面积大于圆面积，但其面积之差小于T-A。也就是圆外切正多边形面积-A<T-A，化简得到

外切正多边形面积<T

但是，外切正多边形（见图10-5）的边心距h等于圆的半径r，而正多边形的周长Q显然大于圆的周长C。因此，

图10-4　证明2（作圆内接正多边形）

图10-5　证明3（作外切正多边形）

这样，就再次出现了矛盾，因为外切多边形的面积不可能既小于又大于三角形的面积。因此，阿基米德推断，假设A>T也是不可能的，圆面积不能小于三角形面积。最后，阿基米德写道：“由于圆的面积既不大于也不小于（三角形面积），因此，圆面积等于三角形面积。”这就是阿基米德的证明。阿基米德用圆面积既不大于也不小于这个特定三角形面积的方法来证明这两个面积一定相等，这种证明方法使一些人感到甚为奇特。一些人感到这种论证方法太绕圈子，对他们我们不妨引述《哈姆雷特》中大臣波洛涅斯的一句话：“这虽则是疯狂，却有深意在内。”

阿基米德处理圆面积的方法，我们就讲完了，重点是学习双重归谬法这种方法。接下来我们学习用Python程序动态地表现，随着内接多边形的边数的增加，多边形近似成圆的过程，如图10-6所示。

图10-6　Python程序动态显示

我们希望实现的效果是：用一个滑动条来表示我们想要画的多边形的边数，从3开始，最大到256，随着用户在滑动条上拖动鼠标，每拖动一下，就对应画一个正多边形，多边形的边数越多，多边形越接近圆，当边数大于32后，多边形就和圆几乎一模一样了。我们想要动态地表现这个变化过程。

分析：

因为我们要画圆内接正多变形，所以首先需要确定各个顶点的位置，假如圆心是（0，0），在圆的半径为r的情况下，圆上任意点的坐标（x，y）为

如果多边形有n条边，那么每个多边形对应的圆心角的角度就是360/n，用弧度表示就是2π/n，每个顶点的坐标可以代入上面公式得到，这里我们用到了正弦和余弦，后面第31章专门讲它们的定义，如果不清楚可以翻到第31章。

在已知多边形的顶点坐标的情况下，用第9章介绍过的画折线图的方法把顶点之间相连就可以得到我们想要的多边形，代码如下：

这个代码出现了几个新的知识点，其中最复杂的一个知识点是事件响应机制和回调函数，第11章专门讲解这部分内容（读者如果有疑惑，可以立即翻到第11章），此外：

（1）第3行，我们从matplotlib.widgets中导入了Slider，Button这种组件，widget是“小玩意，小东西”的意思，这是matplotlib中的一些小组件，可以直接导入就使用。使用方法与第22～24行和第35～36行类似，都是首先用plt.axes（）指定一个区域放置这个组件，第24行创建了一个拖动条，第32行为这个拖动条设置回调函数update（），当检测到变化时，就调用update（）这个事件回调函数，在这个回调函数内部把新的多边形的顶点坐标都重新计算，并用l.set_data（）这个函数把变化的顶点位置传入到折线图中，重新画出来。

（2）第6行，theta=np.linspace（0，2*np.pi，sideNum，False）这个函数是Python中常用的构建自变量序列的方法，linspace的第一个参数是起点，第二个参数是终点，第三个参数是数据点的个数，在本例就意味着，把（0°，360°）的角度按正多边形的边的数量均分，但是注意这里使用弧度来度量一个角度，弧度和角度的度量关系是360°=2π，这与英寸和cm之间的换算类似。这个函数的返回值theta就是一个列表，列表内的元素构成一个角度的等差数列。如下是一个实例：

11　回调函数和事件响应机制

【本章摘要】

数学内容：无。

编程内容：回调函数、事件处理机制。

鼠标的单击、移动，键盘的按键按下等操作，都是对应操作系统的一个事件，应用程序（如视频播放应用、Office等）接收到用户的事件以后需要通过不同的操作进行处理，就需要用到事件处理函数。要讲清楚几种不同的事件处理机制，首先来看一个生活中的实例：用户在麦当劳点餐的过程。

方法一：单队列点取餐过程。

采用这种方法时，所有用户都被要求排成一个长队，餐厅只有一个服务员，她既要收钱，还要制作快餐，然后再交付，点餐过后，服务员就去制作快餐了，客户就等在收银台，后面的人不能继续下一步动作，直到前面的一个人取走了他的快餐，服务员才能处理下一个单子。这种方法客户肯定不满意了，他们会抱怨说：“你们为什么不多雇几个人？收银员只负责收银，厨师只负责制作，配餐员只负责交付，这样不就更快了吗？”好，就采用这种机制。

方法二：单队列通知机制。

这种方法与第一种方法的最大差别是根据工种的不同，每个人负责不同的内容，点餐员只负责点餐，厨师只负责制作，配餐员只负责交付，几个工种之间采用通知机制。几个人之间互相不用等待，随着源源不断的客户进来点餐，点餐员只负责点餐，服务完一位，就继续下一位，订单源源不断地到达厨师的计算机旁，通知厨师进行处理，不同的厨师被通知制作不同的食物。制作好以后，厨师又通知配餐员，菜炒好了，可以交付了。用户看到屏幕上通知自己的餐好了，就可以去取餐台取自己的食物。这种服务机制的效率大大提高，我们来分析一下这种机制的核心特点：

（1）几个人各做自己的事情，互相不等待，互相不依赖。

（2）互相之间只有通知机制，点餐员通知后厨，“做一个汉堡”“做一杯奶茶”“做一份鸡翅”，每个不同的厨师只关注某个特定的事件，比如，做汉堡的厨师完全不关注奶茶和鸡翅的订单；配餐员只负责按订单配餐，她只关注每一个订单的通知，按菜单配餐，然后通知用户取餐。

（3）用户点餐以后，就只关注通知取餐的显示屏，上面出现了自己的订单号，就去取餐。

用计算机术语来重新描述一下第二种点餐过程，如图11-1所示。

图11-1　第二种点餐过程

（1）当有客户进来点餐时，客户的请求就是一个点餐请求事件，点餐服务员就是一个事件处理函数，专门处理点餐请求。点餐服务员由于只处理点餐请求，因此服务过程很快，客户感觉很好，他们的请求很快就被响应了。

（2）点餐服务员把客户的请求变成菜品制作事件，放入事件队列，而厨师就是菜品制作事件处理函数，每收到一个菜品事件，厨师就开始制作菜品。制作菜品的时间比较久，他制作好一个菜品后，往事件队列中放一个菜品制作完成的事件。

（3）配餐员也是一个事件处理函数，他专门处理菜品制作完成事件，他把各种制作完成的菜装入打包盒中，装好一个后，就产生一个订单完成通知事件。用户看到自己的订单完成，就会取餐，完成交易。

（4）在整个点餐运转过程中，一定要有一个事件循环，其职责就是不断地把各种事件收集起来，转发给对应的各种服务员，也就是事件处理函数（或者叫回调函数，回调函数有点类似于你留下一个电话号码在那儿，有事就通过这个电话号码通知你），整个事件循环可以看成餐厅的经理，也可能就是一台计算机。服务员在入职时就进行各种培训，培训完了，就被安排到某个岗位，作为一个事件处理函数，这个过程就是把自己注册为特定事件的回调函数，一旦有某个事件出现，经理或计算机就会调用这个回调函数来处理这个事件。

12　读心术魔术和二进制

【本章摘要】

数学内容：通过魔术引入二进制，并介绍如何把一个二进制数转化为十进制数。

编程内容：用到了多重循环，列表（list），二进制位运算，文件输出，以及pillow模块的Image、ImageDraw两个类。

春节联欢晚会上，来自台湾的魔术师刘谦曾经用“读心术”魔术猜中观众手中纸牌的花色和数字，在全国范围内获得如潮好评。接下来我们也来学习一个“读心术”魔术，学会以后，你也可以公开表演，同样会收获一大波粉丝。

表演时，先准备好如图12-1所示的6张卡片，随机选两名观众上台，要求其中一名观众A心中默想一个小于64的正整数，并写在一张纸上，然后交给旁边的另一个监督官B，在A写的时候你要背过身去并且夸张地捂住自己的眼睛，让台下观众明白你根本不可能看见A写的数字，接着，按图12-1中卡片的顺序向A出示每一张卡片，并请A回答：您的数字在这张卡片中吗？如果回答“在”，就在心中默记一个“1”，如果回答“不在”，就默记一个“0”。6次提问以后，就会得到一个形如“010110”的数字串（注意要按顺序记录，不可颠倒）。用得到的字符串对应再查表12-1（你可以事先打印在一张小卡片上或者直接记住），这个字符串在表中对应的十进制数字就是A心中的数，接下来请监督官B公布结果，毫无意外，每猜必中！

图12-1　读心术魔术卡片

表12-1　十进制数的二进制表示

这6张卡片有什么门道呢？为了讲明白这背后的原理，我们先来学习一下莱布尼兹⁽¹⁾（1646—1716）发明的二进制。

在日常生活中，我们除了经常用到十进制外，其实还经常用到很多其他的进制，比如在时间的计量上，我们用到了：

七进制，星期天（0），1，2，3，4，5，6；

十二进制，1，2，3，4，5，6，7，8，9，10，11，12月；

六十进制，60秒等于1分钟，60分钟等于1小时；

二十四进制，24小时构成1天。

我们观察到，几进制就意味着基本数字就只有几个，比如七进制就只有0～6这七个数字，大于7就进位以后还是用这7个数字来表示比7大的数。

二进制，因为是二进制，所以只有两个基本数字：0、1。一个二进制字串“0101110”，如果用十进制表示，是一个多大的数呢？我们先来考察一下一个十进制数是如何表示一个数量的，比如17801实际上可写成

从右往左，个进位是个位数乘以10的0次方，十进位是十位数乘以10的一次方，……；由此类推二进制，从右往左第一位表示2的0次方，第二位表示2的1次方，第n位表示2的n-1次方。那么“0101110”这个二进制字串表示的就是十进制数46：

表12-2从几个不同的角度对比了一下二进制和十进制。

表12-2　二进制和十进制的对比

有了前面关于二进制知识的铺垫，我们现在来揭秘一下这个魔术。首先我们可以从表12-1中看出小于64的正整数表示成二进制时，只要6个0和1这样的字符，我们把每一个0或1称为一个比特（bit）；反过来，只要知道一个数的二进制表示的每一位0或1，这个数也就确定了。观察图12-1中的第一张卡片，我们发现，这些数字的二进制表示的最高位，即第6位都是1，比如32=“1 00000”，37=“1 00101”；同样，图12-1中的第二张卡片中的数字的二进制表示的第5位都是1，比如16=“01 0000”，21=“01 0101”，依此类推，我们知道每一张卡片中的数的二进制都有一个共同特征，那就是某个特定位都是1；而提问的时候，我们表面上问的是：“您心中的数字在这个卡片里面吗？”而实际上问的是：“您心中的数字的二进制表示的第6位是1吗？第5位是1吗？……”，直到最后一位，至此，读心术猜数字的秘密已经真相大白了。

接下来，我们用Python编程来自动生成这6张卡片，下次需要表演的时候，我们只需要执行一次这个程序就自动生成卡片，省力又省时。

下面的Python代码会自动生成6个文件，每个文件分别对应图12-1中的一张卡片。这段代码的第8行用到了右移的运算，由于整数在计算机上都是用二进制表示，因此可以通过右移再进行“与”运算去获取某个数的特定比特位。

(1)　莱布尼兹是一个可以和牛顿齐名的大科学家，著作涉及数学、力学、机械、地质、逻辑、外交、神学等，他发明了二进制、微积分，著有《二进制算术》等。

13　撒豆子求π

【本章摘要】

数学内容：概率和面积的关系。

编程内容：蒙特卡洛方法求圆周率，圆的参数方程。

在第10章我们直观地看到，随着多边形边数n的增加，多边形越来越接近圆，当n无穷大时，多边形就成了圆，古希腊的数学家阿基米德（公元前287—前212）和中国魏晋时期的刘徽（约225—约295）都是利用这个事实去近似计算圆周率，这个方法会在后面章节详细讲解。今天我们来学习一个如何用计算机模拟撒豆子的过程求圆周率的方法，这个方法很奇妙，看起来风马牛不相及的两件事竟然通过π联系到了一起，而且通过重复毫无难度的撒豆子动作就可以很精确地求圆周率。

学习这个方法以前，我们先来研究一个有趣的问题，往1m²的格子里均匀地撒豆子，落在蓝色扇形区域的豆子的1/4归你，落在红色区域的归我，谁的豆子会多一些（见图13-1）？

如果豆子总数只有3～50粒，那么还可以数一数个数；如果豆子很多，数不胜数，我们就需要换个思路了。既然是均匀地撒豆子，那我们有理由相信豆子是均匀分布在整个区域的，我们比一比二者的面积不就知道了吗？

图13-1　撒豆子

因为蓝色区域的1/4的面积是，而红色区域的面积是，细算一下可知红色区域的豆子略多。这个题目启发我们可以用统计的方法求圆周率。

假如我们在图13-2的方形区域内随便取一点H，其坐标为（x，y），那么根据勾股定理，HO的长度为，容易看出如果一个点的坐标满足，那么这个点一定落在半径为1的圆上，而所有圆内的点都满足，如果随机地往这个方格子里面撒豆子，然后统计有n次落在圆内，m次落在正方形内，二者的比值应该就是其面积之比，所以：

π/4=n/m

π=4*n/m

这就构成一种用计算机计算π的实验方法，即：

（1）随机产生两个在0～1区间内均匀分布的随机数，令其为二维平面内的一个点的坐标（x，y）（相当于在大小为1的方格内撒豆子），每次产生一对随机数后m增加1，即m=m+1。

图13-2　豆子分布区域

（2）判断是否小于1，如果是则n增加1，即落在圆内，即n=n+1，否则不变。

（3）计算一下π=4*n/m，打印π，返回步骤（1）继续循环，直到程序结束。

通过这种方法，循环到达10 000次左右，π已经可以求得非常精确，这个方法的名字就叫蒙特卡洛模拟方法。蒙特卡洛是摩纳哥公国的一座城市，位于欧洲地中海之滨、法国的东南方，是世界著名的赌城，1863年建立的蒙特卡洛大赌场是世界四大赌城之一。蒙特卡洛方法可以看成用计算机不断地按某种概率模型掷骰子，然后统计出现某件事件的概率，因为骰子是一个尽人皆知的赌具，因而用蒙特卡洛这个地名来称呼这个方法。这种模拟计算方法从计算圆周率到制造原子弹的参数计算都会用到，非常有用。

下面的程序除了用蒙特卡洛方法来计算π外，还用一个动画来动态地显示随着模拟点的增加，π越来越精确的过程。

matplotlib.animation是一个动画模块，关于这个模块的详细介绍参见第14章。

如果需要把这个动画存成文件，那么可以把第37行前面的“#”去掉，再改动一下文件存储的路径，使之指向读者有权限存储的路径，就能存成文件，在社交网络上分享。

撒豆子动图

14　GIF动画的原理和animation的使用

【本章摘要】

数学内容：无。

编程内容：GIF动画原理，animation介绍。

我们对于GIF动图其实并不陌生，在微信群里，亲朋好友斗图时用的表情包、各种滑稽的动图背后其实就是GIF格式的图片，它是在一个GIF文件中存储了多帧图片，同时在文件头中指定了播放的速度——帧率，因此播放起来就达到了动的效果，它和电影是同一个原理。帧率是指一秒内播放多少幅图片，电影的帧率至少是24帧／秒，有些高档影院播放的帧率已经达到60帧／秒。这个文件格式的一个好处是几乎所有的终端和应用都支持，如果自己制作了一幅好看的动图，可以到亲朋好友微信群里晒一晒。

我们在第13章用几句简单的Python语句就生成了一个动图，并存成了一个GIF文件，这都是因为matplotlib中有一个动画模块叫animation。使用animation可以产生动画，本节举两个例子来学习一下。

正弦曲线的代码如下，动图如图14-1所示。

图14-1　正弦曲线动图

这个例子中，有几个地方值得注意，在以后使用FuncAnimation（）类来构建动画时也一样要遵循：

（1）第6行和第7行定义一些需要每次更新的数据点，这些数据点会在update（）函数里更新，每次update被调用一次就更新一次。

（2）init（）做初始化，不是必需的。

（3）update（）函数是必需的，这个函数的输入参数是从后面FuncAnimation（）的frames中每次取一个传入；这个函数的输出参数，就是本例中的“ln”，是每次都会被刷新并显示的，需要更新的量一定要返回，动画由此产生。

我们前面学习过用莱布尼兹级数来计算圆周率公式，把它写在下面：

或者用求和符号写成

接下来用动画来示意一下随着n的变大，这个级数和逐步逼近圆周率的过程（见图14-2）。

图14-2　莱布尼兹级数和逼近圆周率的过程

代码如下：

15　质数的分布图形

【本章摘要】

数学内容：质数的性质、质数的判断方法。

编程内容：质数分布的GIF动图。

质数（或素数）就是除了1和它本身以外不再有其他因数的正整数。质数非常重要，以至于每当有人在质数的研究上取得了一个突破都会立即引起全世界的关注，比如早期的华罗庚（1910—1985），20世纪80年代的陈景润（1933—1996），2012年的张益唐（1955—）。张益唐教授完成的论文《素数间的有界距离》，证明了存在无穷多个差值小于7000万的素数对，此结果首次将相邻素数间隔下界（即最小应该差多少的意思）的估计从无限大缩小到一个有限数。因为这篇论文，张益唐从一个默默无闻的大学讲师一跃成为知名大学的正教授，质数究竟有什么魔力？

定理1：每个正整数都可以唯一表示成质数的乘积。

证明：若一个数n是质数，显然成立。若n是合数，则必然可分解成大于1、小于n的两个数的乘积，若均是质数，则得证，否则其中的合数可再分成范围更小的两个数的乘积，直到都是质数为止。所以n是合数时也成立，证毕。

这个定理说明，每个质数都是独一无二的，缺了任何一个质数，整数空间就不完备了，会造成有些整数无法用别的整数的乘积表示出来。除此之外，我们再讲一个质数的应用来说明一下质数的重要性。

在程序员中流行有这么一个说法：在所有重要的算法中，如果只选一个，最重要的算法是哪一个？大部分程序员选的是RSA加密算法。RSA加密算法的核心就是基于这么一个简单事实，即：两个特别大的质数相乘以后新得到一个数，如果不告诉对方这个新生成的特别大的数究竟是哪两个质数相乘而得到的话，对方根本就不可能在有生之年算出来这两个质数分别是什么，即便他使用计算机编程去大量尝试也不行。

人们现在天天在使用移动支付，在互联网上进行各种数据传输，之所以这么放心数据不会被黑客截获并破解，其中最关键的原因就是一些关键数据在传输时都是经过加密处理的，如果没有加密技术，人们对于数据的保密性没有信心的话，数据通信就崩溃了，互联网经济就不可能存在，老百姓根本就不敢把银行卡和支付宝绑定起来。

关于质数的性质和分布，人们还所知甚少，很显然的一个性质是质数没有因子，只能被自己整除，它就像一个怎么砸也砸不破的硬石头。利用这个性质，可以解一些颇难的题目，比如：

题目：已知x+y+2xy=83，x和y都是整数，求x+y等于多少？

解答：这个题目初看起来莫名其妙，完全没有头绪，通常这种题目，你就得往质数的性质上去思考，定睛一看83是个质数，如果能分解成（x-？）（y-？）=83，则可以知道这两个数中必有一个是±1，一个是±83。但是这种分解方式得到的xy前面少了个2，所以把等式两边都乘以2，因此有

2x+2y+4xy=166

（2x+1）（2y+1）=167

由于167仍是质数，那么只有两种可能：2x+1=±1，2y+1=±167，或反之，因此得解。这个题目的关键就是应用了质数不可再分的性质。

质数的分布规律1：大于或等于5的质数一定和6的倍数相邻，例如5和7，11和13，17和19，等等。

证明：令x≥1，将大于或等于5的自然数表示如下：…，6x-1，6x，6x+1，6x+2，6x+3，6x+4，6x+5，6（x+1），6（x+1）+1，…，可以看到，不在6的倍数即6x两侧的数为6x+2，6x+3，6x+4，由于这三类数都可以改写为2（3x+1），3（2x+1），2（3x+2），所以它们一定不是质数，再除去6x本身，显然，质数只可能出现在6x的相邻两侧，即具有6x+1或6x+5的形式。

质数分布是数论中研究质数性质的重要课题，研究各种各样的质数分布状况，一直是数论中最重要和最有吸引力的中心问题之一。关于质数分布性质，通过数值观察、计算和初步研究发现了质数分布的一些现象，比如今天我们来用Python程序画一个质数的分布图形，它表现出了惊人的对称性，这个规律目前还没有经过严格的证明，也许一个看了本书的同学某一天会证明也未可知。

在进入程序以前，我们先来扫清两个基本的问题：即判断一个数是否是质数的方法有哪些？哪一个更快？

15.1　质数的判断算法

算法一：

如果要判断整数n是否是质数，另设一数k，k从2开始一直到n-1，用n对k进行整除运算，如果为零，则数n可以被k整除，则n不是质数，反之则是整数。

Python代码如下所示，其中“%”就是整除运算。

算法二：

第一个算法中为了核实一个数n是否是质数，一共进行了n-2次整除运算，整除运算别看只是一个简单“%”号，但是这个运算非常耗时，所以第一个算法很慢。而简单分析我们知道，如果n能被以下的整数整除，那么n肯定是合数；反之，如果到还不能找到因数，它必然是质数，剩下的数不用再尝试了，因为如果存在因数的话，其中另一个必然比小，而这我们已经尝试过了。因此我们要判断一个数是否是质数，只需遍历到即可，有如下算法：

算法三：

利用我们前面学过的质数一定可以表示成6x+1或6x+5的形式的特点，以及算法二中只需要遍历到就可以判断一个数是否是质数的方法，我们有如下算法：

以上我们提出了三种不同的算法，下面我们来比较三种算法在速度上的表现，注意在调用time.perf_counter（）之前一定要有import time，time.perf_counter（）方法返回性能计数器的值（以小数秒为单位）作为浮点数，即具有最高可用分辨率的时钟，以测量短持续时间。它包括睡眠期间sleep（）经过的时间，并且是系统范围的。通常perf_counter（）用在测试代码时间上，具有最高的可用分辨率。不过因为返回值的参考点未定义，因此我们测试代码的时候需要调用两次，作差值。

程序的输出如下：

可以看出，第一个算法慢得像蜗牛，而第三个算法快如闪电。

15.2　质数的分布

看到图15-1，读者产生了什么样的联想？是玛雅的象形文字？是苗族的手织布图案？答案是质数在二维空间的分布。图15-1中迷人的图像是怎样生成的呢？接下来，让我们用Python来生成GIF动图，把这个美丽图案生成的过程共享给小伙伴们。

这个图案是按如下方法生成的：

（1）选择一个容器深度n，容器深度n意味着这个容器里能装下n个整数，比如当n取6时，那么这个容器里就能装下6个数。

（2）把容器水平放倒，从上往下垒成一个金字塔，第一行放一个容器，第二行放两个容器，一直往下放，从底面看，就构成图15-2所示的图案。

图15-1　质数在二维空间的分布

图15-2　金字塔

质数分布动图

（3）往每个容器里从左往右填数字，第一行由于只有一个容器，因此里面填1，2，3，4，5，6；第二行由于并排有两个容器，因此左边这个容器里填的是7，9，11，13，15，17，右边这个容器里面填8，10，12，14，16，18。照此方法一直往下填数字，直到把容器都填满，如果某个容器里面有质数，容器底部亮红灯，否则亮蓝灯。

这样逐行都填满以后，有趣的几何结构在这种图案中涌现出来。在上面的例子中，每个容器的深度是6，注意容器深度n是一个可以调整的参数。程序代码如下：

练习

x、y为正整数，x+y+xy=18，求x+y的值。

16　如何画五角星和黄金分割比

【本章摘要】

数学内容：黄金分割比，一元二次方程，相似三角形，黄金三角形，斐波那契数列，勾股定理。

编程内容：turtle画图，黄金螺线。

16.1　如何利用黄金分割比来打扮自己

最近社交网络上有一个比谁腿长的游戏，游戏参与者尽力拉开腿部韧带进行纵向劈叉（或横向劈叉），谁拉开的宽度最宽谁就获胜。显然，腿长的人在这个比赛中有优势。但是，从美学的角度，腿并不是越长越好，最美的腿长和身高比其实是有公论的，它等于0.618，也就是黄金分割比。通常是从肚脐齐平往下到脚跟算成腿长，大家可以立即量一下，看看自己的比例是否和这个接近，如果你的比例比这个数字小，可以通过穿高跟鞋的方法调整一下，就像图16-1中这个女孩子一样。如果男生不能穿高跟鞋怎么办呢？也有办法，你可以通过穿高腰的衣服，显出自己腿长。那么黄金分割比的定义是什么呢？

把一条线段分割为较小和较大的两部分，使较大部分与全长的比值等于较小部分与较大的比值，则这个比值即为黄金分割比，例如：

如果定义，容易从上式推导出方程

化简得到如下一元二次方程：

φ²+φ-1=0

这个方程有两个根，负数的根因为不符合实际情况，所以舍去，因此容易求得，即局部和整体之比等于0.618，这就是黄金分割比（见图16-2）。

图16-1　女孩腿长比

图16-2　黄金分割比

黄金分割具有严格的比例性、艺术性、和谐性，蕴藏着丰富的美学价值，这一比值能够引起人们的美感，被认为是建筑和艺术中最理想的比例。在我们日常生活中大家可能会注意到这么一个现象，大型会议的主持人通常不会站在舞台最中央的位置，他们会选择站在舞台的黄金分割点上，这样观众看起来会觉得最舒服。在自然界中，黄金分割比也大量存在，由于自然界是三维立体的世界，所以黄金分割也更多地以二维平面的形式出现，比如黄金螺线、黄金矩形以及黄金三角形。下面我们分别来学习一下。

16.2　如何找黄金分割点

给定一条线，如何精确找到它的黄金分割点呢？黄金分割比φ=，如果能构造出，那么最难的点就已经突破了。如何构造呢？我们知道，这就是勾股定理。这个技巧在本书中会反复用到，大家体会一下勾股定理的妙处。根据这个等式，我们容易得到如下步骤，如图16-3所示。

图16-3　如何找黄金分割点

（1）任意给定一条线段，找到中点。

（2）在任意端点作垂线，以垂足点为圆心，线段的1/2为半径，圆与垂线相交的点就是直角三角形的第三个顶点。这个直角三角形的边长分别是1，1/2，。

（3）以上一步找到的第二个顶点为圆心，1/2为半径，与斜边相交的点把斜边分成了长度为和的两段。

（4）以第三个顶点为圆心，上一步找到的斜边上的长度为半径画圆，圆与线段的交点就是黄金分割点。

16.3　如何画五角星

如何用圆规和直尺画一个正五角星？

要画正五角星，我们先来分析一下正五角星的特点（见图16-4），然后再来看看是否可以通过这些特点来找到画的窍门。通过分析，我们容易知道，正五角星有如下特点：

图16-4　画五角星的方法

（1）五个顶点相连构成一个正五边形，根据多边形内角和等于（n-2）×180°，我们知道这个正五边形的每个顶角为108°。

（2）利用等腰三角形特点，很快可以推出∠GFJ=108°，∠DAC=36°，∠ADC=72°。

（3）DB是∠ADC的平分线。

现在是时候引入黄金三角形的定义了，黄金三角形有2种：

（1）等腰三角形，两个底角为72°，顶角为36°；

（2）等腰三角形，两个底角为36°，顶角为108°。

我们发现，正五角星的顶点互相连线以后，放眼望去全是黄金三角形，是不是瞬间觉得五角星浑身发出金色的光芒？那么为什么命名这种三角形为黄金三角形呢？我们且来研究一下它的特点（见图16-5）。

图16-5　黄金三角形

从图16-5看出，顶角36°黄金三角形的底角，如果平分以后，△ABD和△BDC又都是黄金三角形，由于等腰三角形的特点，有AD=BD=BC，如果假设AD=1，DC=x，那么，

因为

△ABC∽△BDC

所以

那么，

所以

由于x>0，所以。

至此，大家应该明白了，这个三角形为什么叫黄金三角形了，它的底与腰之长之比为黄金比。同样容易推出顶角为108°的黄金三角形的腰与底之长之比为黄金比。

这个三角形为什么叫黄金三角形我们研究清楚了，可似乎也一直没找到画五角星的方法。别急，我们对一个问题或事物研究得越透彻，越容易找到它的窍门，其实我们已经离答案很近了。

既然用圆规和直尺来作图，我们且把五角星的外接圆画出来（见图16-6）找圆心时，很显然我们需要底边的中垂线，我们且把它画出来，这样得到圆心O，大家有没有发现△ODN也是一个黄金三角形？它的证明并不复杂，只要证明顶角是36°就可以了（略）。那么由黄金三角形的性质可知

如果ON=2，，那么CD=？，问题就已经转化成图16-6中的求CD长度了，这个题目并不复杂，可以使用面积法来求：

图16-6　画五角星

四边形ODNC的面积S=CD×ON/2=CD=2S△ODN

进行简单的运算就能得到。既然CD求出来了，那么至此，画五角星的方法已经呼之欲出了，即：画一个圆，它的半径是2，那么只要找到一根弦长度为，由此出发，就可以定出正五边形的每一个顶点位置，不是吗？而构造，应用我们前面刚刚学习过的构造的方法就可以实现。

具体画五角星的步骤如下（见图16-7）：

（1）在白纸上，以任意一点为圆心，以任意长为半径画圆，在圆中画两条互相垂直的圆的直径AB和CD。

（2）取线段OB的中点E，连接CE，设OB=2，那么。

（3）以点E为圆心，以CE为半径画圆弧，交线段OA于点F，那么，。

（4）连接CF，以点C为圆心，以CF长为半径在圆上依次截取长度相等的圆弧。

图16-7　画五角星

（5）连接CM、CH、GN、GM、NH，就得到正五角星。

16.4　黄金螺线

黄金螺线是这么生成的：先水平画一个黄金矩形（宽长之比为0.618的矩形），靠底边以底边为边长在垂直方向作一个正方形，新构成的垂直方向的矩形还是一个黄金矩形，这样一直旋转作下去，同时在作正方形时，以正方形的旋转方向的最内部的顶点为圆心，画1/4个圆，这样一直画下去，就构成了一条黄金螺线。至于为什么叫黄金螺线，对比图16-8（a）和图16-8（b）就一目了然了。

图16-8　黄金螺线

我们接下来就来学习一下如何用Python程序画黄金螺线。程序代码如下：

17　向日葵果实的排列模式和斐波那契数列

【本章摘要】

数学内容：向日葵果实的排列建模，正弦，余弦。

编程内容：黄金分割角度的编程模拟。

斐波那契在研究兔子的生长规律时发现斐波那契数列，但是兔子不可能不死，所以这个数列实际上不能很好地反映兔子的繁殖速度，反而在研究植物的生长规律时，人们有了惊人的发现。

图17-1是一个向日葵开花以后结的果实，葵瓜子密密麻麻、整齐地排在果盘上，仔细观察你会发现每颗瓜子并不是横竖排在一条线上，而是螺旋状地排列。不仅在葵瓜子的果实上，在菠萝、菊花等很多植物的果实上也都表现出了这个规律。从最优化的角度来说，葵花子每生成一个果实，肯定都希望能接受最多的阳光进行光合作用，然后才能让每颗果实都颗粒饱满，因此新生长的果实会和原来的果实错开，彼此都不遮挡，最终达到在有限的果盘上结最多的果实的目的，这样效率最高（神奇的自然界充满了最高效率的生物，每一个生存下来的物种，都经过精心的打磨，以最精美最高效率的形式存在）。基于这个想法，科学家用如下算法来模拟葵瓜子的排列模式。

图17-1　向日葵

假设每个果子的底面积都一样大，设为a，假如能最优排列，那么最终果实能排下来的数量，设为n，应该符合

na=πr²

也可以写成，如果a的大小固定，那么可以把这个公式写成

它反映了一个事实，当n越来越大时，它离圆心越来越远，这非常符合逻辑；由于果实是一颗颗生长的，因此n是一个从1开始增长的变量。前面也提到，每生长一颗果实，这颗果实会偏移一个固定的角度，设为θ，那么我们可以用θ=n·φ来模拟，注意这个φ是偏移的角度，从0°，180°，360°，720°，可以一直增加，向日葵模型如图17-2所示。

图17-2　向日葵模型

人们惊讶地发现，当每次转动的角度是137.5°时，排列效率最高。图17-3中，（a），（b），（c）三幅图分别对应的转动角度为137.3°（见图17-4），137.5°，137.6°，尽管转动角度差别如此细微，颗排列效率差别却非常大，而137.5°=360°-0.618×360°，也就是说，每次转137.5°（等同于反方向转222.5°）的位置新排一颗果实，最终会把果盘排满而没有空隙（见图17-5），其他的角度都会留下空隙，黄金角度0.618出现了，如果你眼力足够好，你可以数一数顺时针的螺旋方向的果实个数，然后再数一数逆时针方向的果实个数，你会惊讶地发现它们的比值又是0.618，多么令人难以置信，我们现在就编程来验证一下。

图17-3　黄金角度

图17-4　移动的角度

图17-5　向日葵果实排列

下面的程序模拟了葵花子的生长模式。

18　妈妈的拼图手艺和面积不变几何形变

【本章摘要】

数学内容：根式，勾股定理，尺规作图，转动的数学模型。

编程内容：三角形的转动模型。

小时候我母亲喜欢收集做衣服剩下的碎布片，不管多么不规则，她都要好好地收藏起来。起初没人知道她究竟要干什么，直到后来她把那些花花绿绿各色的碎布片拼成了一整张百色花布的时候（见图18-1），大家才明白，碎布片也可以绽放花一样的美丽。后来大家都学她仔细收好每一片碎布，但是要做到巧妙利用各种碎布构成图形，还真的不是那么容易，其中甚至包含了很多数学知识呢。

图18-1　花布

比如：

（1）一个三角形如何变成一个长方形？

（2）一个不规则的四边形如何变成长方形？

（3）一个长方形如何变成正方形？

（4）一个三角形如何变成一个正方形？

下面我们来逐个研究。

18.1　三角形变长方形

假设有一块三角形的布，边长分别是a，b，c，假如要变成和三角形某一边长相等的长方形，如图18-2所示，那么我们应该如何裁剪一点也不会浪费布料呢？我们知道三角形的一个面积公式是，其中h是这个三角形的高，由这个公式提示就可以按图18-2所示找到两条边的中点，连线剪开，然后再按图示方法剪开并移位就可以拼成一个长方形的布片。

图18-2　三角形变长方形

18.2　不规则四边形变长方形

把不规则四边形变成长方形有两种方法，第一种方法就是把四边形拆成两个三角形，任意选择一条对角线为底，如图18-3所示。

图18-3　不规则四边形变长方形

步骤如下：

（1）连接AC。

（2）分别过B、D作AC的垂线段。

（3）分别作△ACB、△ACD的中位线PQ、MN。

（4）旋转四个标号三角形。

第二种办法（见图18-4）切割次数更少：

（1）找到每条边的中点，任意连接两条对边的中点。

（2）从剩下来的中点向步骤（1）中的中线作垂线HM，QP。

（3）沿HM、FP、NP、QP剪开。

（4）四边形②保持不动，四边形①以H为圆心旋转，直到一条边和四边形②重合，四边形④以Q点旋转，直到一条边和四边形③重合，然后四边形③以F点为圆心旋转，直到边与②的一条边重合。这样旋转以后，新构成的四边形是一个矩形。

图18-4　不规则四边形变长方形（第2种切割方法）

从这个形变，我们知道任意四边形的面积都可以用一条中位线的长度乘以另两个到中位线的高来计算。

18.3　长方形变正方形

如何把一个长方形变成正方形呢？设长方形的边长分别是p和q，容易知道我们需要找到边长为的正方形。这里我们需要利用一些等式变换的技巧，可知

进一步可变形为，因此有

变形到这里，我们是否觉得有点眼熟？是的，这是勾股定理的形式！这提示我们只要能找到和两个长度，就可以利用勾股定理，找到一个直角三角形，它的最后那条直角边的长度就是。这个作图过程很简单，此处不再赘述。

更简单的办法是直接以p+q为直径作圆，然后在交接处的B点作垂线，和圆交于点F，。这个证明过程很简单，因为△ABF∽△FBD，因此直接可以得出这个结论。由此也可以推出一个结论：如果p，q可以用尺规作出来，那么也可以用尺规作图得出。其实还有个专门的术语叫“p和q的几何平均”，为什么叫这个名字？因为它有如图18-5所示的明确的几何含义。

图18-5　勾股定理

找到正方形的边长以后，长方形变正方形只需一刀，就可以解决问题。

18.4　正三角形变正方形

正三角形如何变成正方形？假设正三角形边长为a，容易求得其面积是，那么要拼的正方形的边长为。如果要构造这个边长出来，也需要构造好切割方法，才能拼出一个正方形。如果从构成正方形来考察，显然三角形需要被切成4块，以及需要构造4个直角。参考前面我们把不规则四边形变成长方形的方法，我们是否可以直接如法炮制呢？找到每条边的中点，直接连起来切割，显然不行，由于三角形只有三条边，所以必然要在一条边上找两个点。假定第一刀我们就任意切，比如切成DF，那么为了构造直角，我们肯定需要从点E作一条垂线到DF交于点H，如果我们以点E为圆心，旋转过后发现2EH就构成正方形的一条边，显然EH的长度不能随便，如果EH确定，EH⊥DH，那么DF就构成以EH为半径的圆的切线。

综合以上的几个要求，必然会形成类似于如图18-6所示的切割方法。

图18-6　正三角形变正方形

将EHFB绕点E顺时针旋转180°，将DIGC绕点D逆时针旋转180°。我们就拼出图18-6（b）中下面3块。

于是，只要直角三角形斜边FG等于FB与GC的长度和，我们就至少拼成了一个长方形。而如果我们选择F、G点，使得EH恰好等于正方形边长的1/2，则根据面积关系，所拼成的图形一定是一个正方形。

因此我们可以用如下5个步骤作出正确的正方形：

（1）以AB的中点E为圆心，以正方形边长的1/2为半径作圆。

（2）从AC的中点D作圆的切线，将位于正三角形内的切点记为H。

（3）沿长DH，将它与底边BC的交点记为F。

（4）找出底边BC上的点G，使得FG等于底边边长的1/2，因为BF+GC=FG。

（5）由点G作DF的垂线，交DF于点I。

至此，只差一步之遥，即怎么用尺规作出。我们从上一节的结论知道，如果p可以用尺规作图法作出，那么就可作出，重复此过程就知道完全可作出。步骤如下：

（1）首先以a为直角边，作一个等腰直角三角形，斜边为。

（2）以a为直角边，可得斜边为。

（3）以为直径作圆，在a处作垂线，和圆相交的点和垂足点之间的线段长就是。

18.5　用Python实现三角形旋转180°

本节我们用Python程序把一个三角形旋转180°，读者可以试着以此程序为基础，把18.2节的不规则四边形变成一个长方形的过程用动图（见图18-7）表现出来。

我们来分析一下这个不规则四边形要变成长方形需要哪些操作：

（1）切分成①，②，③，④四个四边形；

图18-7　用Python程序把一个三角形旋转180°

（2）四边形①以H为圆心顺时针旋转180°；

（3）四边形④以Q为圆心逆时针旋转180°；

（4）四边形③和④一起以F为圆心逆时针旋转180°。

通过以上分析可知，如果我们能用程序完成对任意多边形以给定点为圆心任意旋转某个角度，那么在这个程序基础上，就能实现任意四边形等面积变成长方形。下面我们来分析用Python如何实现多边形的旋转。

多边形在平面上实际是一些顶点的连线，所以多边形的旋转实际上就是顶点在平面内的旋转，因此实现多边形的旋转又可以简化成顶点的旋转。我们先来学习一下一个点绕原点进行旋转，其坐标如何变化的。假如点v坐标为（x，y），它和原点的距离为r（见图18-8），则其坐标和角度的关系是：x=rcosϕ，y=rsinϕ。旋转角度θ后，得到v'点，假如其坐标为（x'，y'），则

x'=rcos（θ+ϕ）=rcosθcosϕ-rsinθsinϕ=xcosθ-ysinθ

y'=rsin（θ+ϕ）=rsinθcosϕ+rcosθsinϕ=xsinθ+ycosθ

如果我们想要以任意点（cx，cy）为圆心进行旋转，则可以先把原点移到（cx，cy），套用上面的公式，再把原点移回去，不就可以了吗？因此容易把公式写成

x'=（x-cx）cosθ-（y-cy）sinθ+cx

图18-8　多边形旋转

y'=（x-cx）sinθ+（y-cy）cosθ+cy

多边形转动的代码如下：

其效果如图18-9所示。

图18-9　转动效果

转动效果动图

19　用勾股定理画一朵花——勾股之花

【本章摘要】

数学内容：勾股定理、圆和勾股定理的关系。

编程内容：勾股定理的图形化。

勾股定理在西方被称为毕达哥拉斯定理（Pythagoras Theorem）。从名字上看，很多人认为是中国人首先发现并证明了这个定理，其实如果梳理一下勾股定理在文献记载中的时间脉络，我们就会有自己的结论：

（1）远在公元前约3000年的古巴比伦人就知道和应用勾股定理，他们还知道许多勾股数组。美国哥伦比亚大学图书馆内收藏着一块编号为“普林顿322”的古巴比伦泥板，上面就记载了很多勾股数。古埃及人在建筑宏伟的金字塔和测量尼罗河水泛滥后的土地时，也应用过勾股定理。

（2）公元前11世纪，周朝数学家商高就提出“勾三、股四、弦五”。《周髀算经》（约成书于公元前1世纪）中记录着商高与周公的一段对话。商高说：“……故折矩，勾广三，股修四，经隅五。”意为：当直角三角形的两条直角边分别为3（勾）和4（股）时，径隅（弦）则为5。以后人们就简单地把这个事实说成“勾三、股四、弦五”，根据该典故称勾股定理为商高定理。

（3）公元前6世纪，古希腊数学家毕达哥拉斯证明了勾股定理，突破了以前人们对于勾股定理还停留在一些特例的认知。

（4）公元3世纪，三国时期的赵爽对《周髀算经》内的勾股定理作出了详细注释，记录于《九章算术》中“勾股各自乘，并而开方除之，即弦”。赵爽创制了一幅“勾股圆方图”，用形数结合的方法，给出了勾股定理的详细证明。

勾股定理的应用非常广泛，它可以用于已知直角三角形两边求解第三边，或者已知三角形的三边长度，证明该三角形为直角三角形或用来证明该三角形内两边垂直。

勾股定理的证明有几百种之多，所有的证明方法加起来可以写一本书。那些内容很有意思，但和我们的生活并不直接相关，我们知道，勾股定理的直角几何解释就是图19-1。

图19-1　勾股定理的直角几何解释

这两个小正方形的面积等于大正方形的面积。这个大家都能接受，但是如果换到实际生活中，可能小朋友们就会犯错误，不信我们来看看以下这个例子。

问题：

假设有一天你饿了，进比萨店点了一个9英寸的比萨。在你等的时候，服务员告诉你，对不起，9英寸的比萨卖完了，我们换成两个6英寸的比萨给你，两个6英寸的比萨加起来有12英寸，比9英寸大多了，可以吗？

勾股定理，我们直观的感觉就是满足勾股定理的三个长度，肯定能构成一个直角三角形，反之也成立。不被大家意识到的是，勾股定理其实也可以理解成一个圆，我们来看一下怎么回事。

如果斜边边长固定，直角边边长可变，大家会发现直角三角形直角顶点构成一个圆，圆的直径就是斜边边长，如图19-2所示。这个结论的证明非常简单，把圆心和直角三角形顶点一连，就立即可以证明。

图19-2　将勾股定理理解成一个圆

勾股定理除了前面提到的作用，本书更关注的是勾股定理有什么好玩的，或者好看的。嘿，还真有一个办法可以用勾股定理画出一朵花来，我们把它叫作“勾股之花”。

首先看看我们如何找到所有的勾股数。所谓勾股数，就是要寻找3个正整数，使得它们满足

a²+b²=c²　或　a²-b²=c²

有人已经想到了一个巧妙的办法，已知

（x+y）²-（x-y）²=4xy

如果右边也能写成（）²，那么就成为上面写的勾股定理形式了。因此，如果设x=m²，y=n²，那么（x+y）²-（x-y）²=4xy，就写成了

（m²+n²）²-（m²-n²）²=（2mn）²

这个公式就提示我们找勾股数的算法：

当m，n为任意正整数，且m>n，m²+n²，m²-n²，2mn，构成勾股数（见表19-1）。

表19-1　m，n构成勾股数

下面我们就用这种方法找到勾股数并用它来画一朵花。

图形化的方法：

由小到大，任意一组勾股数的长的直角边和上一个勾股数的斜边叠在一起，保持直角三角形的小角都在原点，这样一直叠下去，就会构成一个好看的螺旋。就像图19-3一样，在画的时候，可以不断地变换三角形的颜色，这样就会色彩斑斓。

图19-3　勾股之花

生成勾股之花的代码如下：

20　倒数勾股定理和贝塞尔问题

【本章摘要】

数学内容：倒数勾股定理、贝塞尔问题、转动的数学模型。

编程内容：三角形的转动模型。

前面我们学习了勾股定理，其实还有另一个形式的勾股定理——倒数勾股定理（inverse Pythagoras Theorem），倒数勾股定理（见图20-1）是这样的：假如一个直角三角形的边长分别是a，b，c，斜边上的高是d，那么，

图20-1　倒数勾股定理

倒数勾股定理的证明很简单：

由面积公式知道ab=cd，代入上式就得到倒数勾股定理。

倒数勾股定理有什么用呢？它有什么物理意义？非常好的问题。其实倒数勾股定理和我们的生活息息相关，我们每天都在和这个规律打交道。我们每天都要用灯照明，离得远了，光照强度就不够了；我们和人说话时，走远了，就听不清了，所有这些我们接收到的信号的强度都随距离的平方成反比，可以用这个函数关系来表示：

这个道理非常好理解，我们可以用图20-2来描述。

图20-2　倒数勾股定理的应用

在不考虑光损耗的情况下，在距离为r这个地方，面积为S（比如我们的眼睛的视网膜的大小）的屏幕接收到的光的能量为E，当距离为2r时，同样多的能量要被4S的面积接收，所以在2r这个距离上同样面积为S的平面上接收到的能量就只有r这个位置的1/4了。回过头来，我们来看看倒数勾股定理有什么含义。假如人站在图20-1的A点，我们在D点放了一盏灯，这个人觉得光照强度正合适。如果出于某种原因，我们需要调整成两个灯（比如说恰好有个东西会挡住光线，或者说手会造成影子），那么为了达到同样的强度，需要在B和C这两个地方各布置一盏一模一样的灯，B点和C点距离约束就是倒数勾股定理，声音、光线都遵循这个规律。是不是突然觉得在布置灯光和音响的时候，倒数勾股定理很有用了呢？

20.1　贝塞尔问题

贝塞尔问题在1644年由意大利数学家蒙哥利（Pietro Mengoli）提出，近百年的时间里，数学家都不知道答案，直到1735年大数学家欧拉出手。贝塞尔问题就是求自然数的倒数的平方和，写成数学表达式就是

下次如果碰到有人自称数学学霸，不妨拿贝塞尔问题去挑战一下他，万一他知道答案，就要他证明一下。万一，我说万一，他真的也能证明，那么恭喜你，你有一个能和欧拉匹敌的天才同学，赶紧搜集他的一切趣闻轶事、纪念品，某天他一定会名扬世界。

1735年欧拉得出的结果是

欧拉的文章和结果都石破天惊，它有以下四个不同寻常之处。

（1）它很简洁，令人难以置信地漂亮。

（2）竟然和π联系到一起。

（3）无穷多个有理数相加竟然是个无理数，为什么这个结论石破天惊？因为我们学习过，有理数相加还是有理数这个结论，现在被颠覆了！

（4）欧拉的证明方法出人意料，天才尽显。

今天，贝塞尔问题的证明方法有很多，但基本都要用到高等数学的知识。我们下面来看一种几何方法的证明（注意我们学习的方法不是欧拉的证明方法），就要用到倒数勾股定理。

首先，我们来看看这个问题的等价形式，如果能够证明其中的等价形式，也就等同于证明了贝塞尔问题。

如果欧拉的结论成立，那么把除以4，则得到

由于这个操作实际上是把级数分成了奇数和偶数部分，因此，也可以知道，如果上式成立，那么就等价于

接下来我们给出这个问题的几何证明。

如果我们画一个周长为2的小圆，那么小圆的直径，如图20-3中的AO=2/π，在小圆外再画一个周长为4的大圆，圆心为O，这两个圆的垂直直径同线，过O点作小圆的切线，那么根据倒数勾股定理，可知

可以理解成O点的一个灯塔，等效地替换成了B点和C点的两个灯塔，B点和C点之间的弧长为2，即，。

图20-3　等效变换（一）

如果我们再在刚才的两个圆外又作一个大圆，直径再扩大一倍，目的就是把B点和C点的灯塔再等效变换到更大的圆上，如图20-4所示，我们看看会发生什么？

可以发现：

图20-4　等效变换（二）

因为DF⊥EG（至于为什么垂直，因为BC是内圆的直径，圆上任意一点和直径构成的夹角是直角，这是初三的内容，证明也极容易，故略）

所以D、E、F、G四个点均匀分布在最大的这个圆上

所以，

如果直接用弧长代替弦长（目前这还不准确，但是已经能发现规律了），则会得到

重复同样的过程，这个外圆的直径会越扩越大，最终会接近成一条直线，等效的灯塔数越来越多，而从A点到各个灯塔的距离越来越长，这时弧长和弦长就没有差异了，灯塔和灯塔之间的距离保持2的特点不变，最终会得到如下等式：

换一种写法就是

贝塞尔问题得证。

对于科学研究，我们要大胆假设，小心求证。欧拉最初求出贝塞尔问题的答案时，他对结果也不是很自信，于是他直接用最原始的方法算了一下，大概加到了20的平方，发现和他的结果非常接近，于是他发表了他的论文。

我们现在也编程验证一下这个结论。

20.2　贝塞尔问题的实验验证

我们用Python程序模拟从1算到100，如图20-5所示，结果快速接近欧拉的值，算到40时，差距已经比较小了。

图20-5　贝塞尔问题的Python程序模拟图

贝塞尔问题动图

代码如下：

21　三次方程解法打擂台的故事和如何开根号

【本章摘要】

数学内容：手动计算平方根的方法，衡量逼近质量的数学方法，杨辉三角。

编程内容：牛顿下山法求任意次方根。

有了计算器，已经没几个人会真正用手去计算平方根了，但是在40年前计算器是一件稀罕物的时候，计算技能仍然是人人必学的。时间再退回到16世纪初期，中国的明朝中期（那个时候，中国还是世界老大，但是欧洲正在快速崛起），那时候求解三次方程的解析式仍然是世界难题，连数学家都不会。意大利数学家德尔·费罗第一个发现三次方程根的解法，但是只把自己的发现教给了几个弟子，同时要求严格保密。直到他去世，这个秘密一直保守得很好，原来那个时候盛行打数学擂台，擂台赛通常附带高额的赏金，比赛中常出现的难题就是各种各样的三次方程，这种名利兼收的事情，数学家乐此不疲！德尔·费罗有一个好卖弄的弟子叫德尔·费奥雷，就经常向意大利的其他数学家挑战，内容当然都和三次方程有关，最后总是他赢，赢得多了，大家就怀疑，三次方程已经被他破解了。1535年，威尼斯数学家尼科洛·塔尔塔利亚接到了费罗的“战书”，他面对德尔·费罗提出的三次方程问题绞尽脑汁，竟然在擂台赛截止日期之前也发现了三次方程的解析式。然而，他同样拒绝公布他发现的秘密。

4年后，这场混战传到了米兰数学家吉罗拉莫·卡尔达诺（Girolamo Cardano，1501—1576）那里，他软磨硬泡并向塔尔塔利亚发誓永不发表这种方法，塔尔塔利亚给了他三次方程的解析式（缺了证明过程）。在接下来的几年里，卡尔达诺专心致志地攻坚这个问题，最终也成功了，他的学生之一卢多维科·费拉里甚至归纳出了四次方程的解法。

1542年，卡尔达诺与费拉里偶然间拜访了德尔·费罗的另一位学生，意外发现德尔·费罗才是第一个解开三次方程的人。于是，卡尔达诺认为，自己发过的誓言应该是无效的，他在1547年发表了《大术》，公开了三次方程的解法。另外，塔尔塔利亚认为卡尔达诺剽窃了自己的研究成果。可惜，卡尔达诺已经被公认为第一个征服三次方程的人，直到今天为止，三次方程的解析式依然以卡尔达诺命名，称为“卡当公式”。

现在我们学习一下如何开根号，如果你有机会穿越回16世纪，你也可以用这个技能打擂台呢！

21.1　如何开平方根

问题：17的平方根是多少？我们简单地猜测一下知道它比4大，比5小，先尝试一下4.1，那么，

4.1×4.1=16.81

误差为

17-16.81=0.19

加0.1再计算：

4.2×4.2=17.64

误差为

17-17.64=-0.64

那我们现在知道，结果应该是比4.1大，比4.2小。

继续猜猜4.11，4.12，…，猜的过程如下。

算法一

（1）猜测一个上界，一个下界。用上界和下界的1/10作为步长，加一个步长，计算误差。

（2）看猜大了，还是猜小了。

（3）如果猜小了，再增加0.1，计算误差。

（4）如果猜大了，就减小0.1，计算误差。

（5）如果误差足够小，则结束，否则重复步骤（2）。

这个算法可以称为“变步长尝试法”。

要是我们能比较确切地知道下一次需要增加／减少的步长是多少比较合适就好了，可以避免算法一的简单尝试。

我们知道：

（a+b）²=a²+b²+2ab=c²

刚才我们猜平方根为4.2的时候，实际过程就是

（4.1+0.1）²=4.1²+0.1²+2×4.1×0.1

我们发现后面增加的0.1的平方很小，也就是b²很小，2ab这个量反而比较大，也就是说，我们每次调整校正值b时，其实增加了大概2ab这么多，b²可以忽略。如果结果越来越精确，那么b就变得越来越小，b²就更小了，那么，

17的平方根=（随便猜的数a+校正b）²=a²+b²+2ab≈a²+2ab

得到

b=（17-a²）/2a

上面这个式子不就是告诉我们应该尝试增加多少吗？我们验证一下。

比如上面这道题，我们第一次猜a是4，那么校正值b=（17-（4×4））/（2×4）=0.125，校正后的结果4+b=4.125，核算一下发现4.125×4.125=17.015，已经很接近17了。可以看到，我们用b=（17-a²）/2a这个公式一下子就从4快速进步到4.125（注意，比刚才的4.1猜的好得多）。

现在我们已经知道4.125比较准了，可以重复刚才的过程，把猜测值的起点设为4.125了，那么校正值b=（17-（4.125×4.125））/（2×4.125）=-0.0019，校正后的结果4.125-0.0019=4.1231。这次要我们猜4.1231。核算一下发现4.1231×4.1231=16.999 95，已经非常非常接近17了，新的方法只猜两次就到了这个精度。这个算法叫“牛顿下山法”，它有一个图形化的解释，我们来看图21-1。

图21-1　已知正方形的面积求边长

求某个数的平方根，实际是就是已知一个正方形的面积，求边长，在图21-1中就是求c的长度。我们把它分成了猜测值a和理想的校正值b，在求比较合适的校正值b应该等于多少时，我们忽略了b²，实际就是忽略了图中阴影部分那个正方形的面积，用a²这个正方形和紧挨着的两个长方形面积来近似c²，即用a²+2ab来近似c²。因为b²+a²+2ab=c²，假设，那么，这不就是勾股定理吗？所以这个等式一定可以对应画出一个直角三角形，它的斜边为c，两个直角边一个是b，另一个是，我们利用圆的性质可以把它容易地画出来。可以看出，当忽略b²时，实际上就是用直角边近似代替斜边c，也就是用黑色正方形面积去近似正方形面积，忽略了阴影部分面积，这里我们又一次看到了经典的勾股定理的图形。

下面我们来把这个方法写成一个求平方根的程序，并把误差快速降到0的过程图形化表示出来，如图21-2所示。

图21-2　牛顿下山法

为什么叫牛顿下山法？这个结果的收敛过程像不像一个山坡？

作业：如何求立方根？

21.2　如何求高次方根

平方根我们会求了，那么高次方根应该怎么求呢？回想一下上一节求平方根的思路，我们的思路是这样的：

（1）把结果拆分成猜测值+校正值。

（2）进行二次项展开，忽略校正值的二次方，求出校正值。

（3）计算新的猜测值的误差，计算误差精度。

（4）如果达标，则结束，否则返回步骤（1）。

这个过程中，最核心的是进行二次项展开，忽略不重要的校正项的二次项，计算校正值，三次方根甚至更高次根式是否可以同样处理呢？答案是肯定的，但是具体如何做我们首先要解决高次二项式的展开公式。

图21-3在中国叫杨辉三角，在国外叫帕斯卡三角，在中国南宋数学家杨辉于1261年所著的《详解九章算法》一书中出现，是二项式系数在三角形中的一种几何排列。帕斯卡（1623—1662）在1654年发现这一规律，比杨辉要迟393年。杨辉三角是中国古代数学的杰出研究成果之一，它把二项式系数图形化，把组合数内在的一些代数性质直观地从图形中体现出来，是一种离散型的数与形的结合。系数的含义就是二项式展开项的系数的顺序排列，具体如图21-4所示，大家可以验证一下。

图21-3　杨辉三角（帕斯卡三角）

图21-4　二项式展开项的系数排列

杨辉三角有一些有趣的性质，如图21-5所示，包括：

（1）每个数都等于它肩上的两个数的和。

（2）斜着看，分别构成等差数列和高阶等差数列（相邻两元素之差是等差数列）。

图21-5　杨辉三角的部分性质

杨辉三角就可以用于求高次方根，我们以四次方的展开式为例：

（a+b）⁴=a⁴+4a³b+6a²b²+4ab³+b⁴

仍然遵循前面的假设，a为猜测值，b为校正值，为了求b应该调整为多少比较合适，应该怎么对这个公式进行简化？

这里我们直接给出方法：把b的平方项、立方项、4次方项等b的所有高次方项都忽略掉，这样上式就可以近似写成

（a+b）⁴≈a⁴+4a³b

那么就能立即推导出

这个方法适应于任意次方的求根方法。它的编程方法，由于和求平方根的程序大同小异，我们就不重复，我们来尝试用程序生成杨辉三角的系数：

作业：编程求立方根。

22　将军饮马问题的动图模型

【本章摘要】

数学内容：对称和勾股定理，两点之间直线最短。

编程内容：ImageDraw实现GIF动态的方法。

传说古希腊的亚历山大城有一位精通数学和物理的学者，名叫海伦。一天，一位罗马将军专程去拜访他，向他请教一个百思不得其解的问题。将军每天从军营A出发，先到河边饮马，然后再去河岸同侧的B地开会，应该怎样走才能使路程最短？这个问题的解决并不难，据说海伦略加思索就解决了它。把这个问题抽象为数学模型：如图22-1所示，直线l同侧有两个定点A、B，请在直线l上找一点C，使AC+BC最小。假设点A、B在直线l的异侧就好了，这样我们就可以利用两点之间直线最短找到点C的位置了，即连接AB交直线l于点C。

图22-1　将军饮马问题

因此，我们可以找点A关于直线l的对称点，连接A'B交直线l于点C，点C即为所求。

如图22-2所示，如果将军在河边的另外任一点C'饮马，所走的路程就是AC'+C'B，但是AC'+C'B=A'C'+C'B>A'B=A'C+CB=AC+CB，故在点C处饮马路程最短。

掌握了将军饮马问题的模型，我们来学习一些将军饮马的问题变种，比如：

（1）如图22-3所示，点P是∠MON内的一点，分别在OM，ON上作点A，B。使△PAB的周长最小。

图22-2　问题求解

图22-3　将军饮马问题变种（一）

（2）如图22-4所示，点P、Q为∠MON内的两点，分别在OM，ON上作点A，B。使四边形PAQB的周长最小。

（3）如图22-5所示，点A是∠MON外的一点，在射线ON上作点P，使PA与点P到射线OM的距离之和最小。

（4）如图22-6所示，点A是∠MON内的一点，在射线ON上作点P，使PA与点P到射线OM的距离之和最小。

（5）在a，b，c，d，e为正数，c，d，e为常数，且a+b=c条件下，问：何时值最小，且最小值为多少？

图22-4　将军饮马问题变种（二）

图22-5　将军饮马问题变种（三）

图22-6　将军饮马问题变种（四）

这个题目比较怪异，初看完全没有头绪，但是如果我们对勾股定理比较敏感，我们应该立即意识到，这两个根号其实都可以表示成直角三角形的边长，整个题目可以图形化成图22-7所示图形。

图22-7　将军饮马问题图形化

这个题目图形化后，发现还是将军饮马问题！

我们现在来尝试用Python制作一个将军饮马的动图课件，展示随着饮马点的不同，距离的变化，让人一目了然地了解距离变化规律（见图22-8）。

图22-8　将军饮马问题动图

将军饮马动图

代码如下：

23　排序的几种算法

【本章摘要】

数学内容：排序的几种算法，冒泡排序，选择排序，插入排序。

编程内容：排序算法的动态过程，不同算法的思路和优化过程。

23.1　冒泡排序

思路分析：在要排序的一组数中，对当前还未排好的序列，从前往后对相邻的两个数依次进行比较和调整，让较大的数往下沉，较小的往上冒。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端（升序或降序排列），就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样，故名“冒泡排序”（Bubble Sort）。

冒泡排序的算法原理（从后往前）：

比较相邻的元素。如果第一个比第二个大，就交换它们两个。

对每一对相邻元素做同样的工作，从开始第一对到结尾的最后一对。这样第一轮完成以后，最后的元素应该会是最大的数。

针对所有的元素重复以上步骤，除了最后一个。

持续每次对越来越少的元素重复上面的步骤，直到没有任何一对数字需要比较。

即每当两相邻的数比较后发现它们的排序与排序要求相反时，就将它们互换。

图23-1用一个实例表示了这个过程，注意第一次找到了8这个最大的数并排在最后面；第二次循环的时候，8可以排除在外，不用比较；第三次可以把8，5都排除在外了，以此类推。

图23-1　冒泡排序

从这个算法可以看出，每次比较，为了找到最大的那个数，要做多次交换，比如我们看例子中的5，它从第1个位置被交换到第2个位置，然后又交换到第3个位置，最后到达第4个位置，这就是它的最终位置。于是就想，它能否一次直接就交换到第4个位置呢？答案是可以的，这个方法就是选择排序。

23.2　选择排序

选择排序（Selection Sort）算法的基本思路是为每一个位置选择当前最小的元素。首先从第1个位置开始对全部元素进行选择，选出全部元素中最小的给该位置；再对第2个位置进行选择，在剩余元素中选择最小的给该位置即可；以此类推，重复进行“最小元素”的选择，直至完成第n-1个位置的元素选择，则第n个位置就只剩唯一的最大元素，此时不需再进行选择。选择排序最多只需要n-1次交换。图23-2用一个实例表示了这个过程。

图23-2　选择排序

23.3　插入排序

插入排序是将无序序列中的数据插入到有序的序列中，在遍历无序序列时，首先用无序序列中的首元素与有序序列中的每一个元素比较并插入到合适的位置，一直到无序序列中的所有元素插完为止。如图23-3所示，对于一个无序序列arr{4，6，8，5，9}来说，我们首先确定首元素4是有序的，然后在无序序列中向右遍历，6大于4则它插入到4的后面，再继续遍历到8，8大于6则插入到6的后面，这样继续，直到得到有序序列{4，5，6，8，9}。

图23-3　插入排序

图23-4表示了一个完整的插入排序的过程，注意图中白色框中的数字是无序序列，还没有排列号的数，而灰色的就是已经排好序列的有序序列，不断地从白色框中拿出一个数插入灰色的序列中。在算法的开始，假定第一个是有序序列的第一个数字。

图23-4　完整的插入排序

上面分别讲解了几种排序的算法，下面我们来练习用程序图形化排序的过程。首先，我们产生有任意个数字的自然数，从1开始，然后把它们的次序打乱，变成一个随机排列的序列。在二维数轴上排列，X轴表示其排序位置，Y轴表示其大小，那么开始就可能是图23-5这个样子，而使用不同的排序算法，随着算法的推进，这个混乱的序列就被排序成最后的样子，见图23-6。

图23-5　程序图形化冒泡排序（开始）

图23-6　程序图形化冒泡排序（最后）

插入排序动图

代码如下：

这个代码中出现了一些以前没有出现过的语法现象，我们来介绍一下。

在循环体中，出现了yield语句。yield本身可以理解成每次程序执行到这里，就立即把函数的状态记住，保护好现场，记住函数里面变量的状态，然后就返回，下次再进入时就从断点处恢复现场，重新开始执行。

举个简单的例子，比如下面的代码：

24　用割圆术计算π

【本章摘要】

数学内容：相似三角形，刘徽割圆术。

编程内容：刘徽割圆术的Python实现。

在前面章节我们介绍了一种蒙特卡洛法求π，提到了古希腊的阿基米德和中国的数学家刘徽都用割圆术求π，今天我们来学习一下他们的方法。

俗话说得好，不见兔子不撒鹰，在尝试去求这个常数以前，我们肯定要知道为什么存在这么一个常数。那为什么π是一个常数？即为什么所有圆的周长和直径之比为一个定值，这一点似乎并不能够自然而然地就得到。因此在寻找这个常数之前，先要做的应当是证明“圆的周长与直径之比确实是一个常数”。

如图24-1所示，以点O为圆心作两个半径不同的圆，小圆的半径为r₁，周长为c₁；大圆的半径为r₂，周长为c₂。分别作两个圆的内接正n边形（n为偶数），边长分别为k₁和k₂，且保证两个正n边形过圆心的对角线重合。

那么有，∠AOB=∠COD，因此△AOB∽△DOC。所以有。

图24-1　作圆的内接正n边形

设小正n边形和大正n边形的周长分别为和，则有，。

所以有。

由于当n→∞时，，，即取极限或者说是逼近的思想，当边数区域无穷，内接多边形就近似是一个圆了，后面寻找π时还会再次用到这个思想。

所以就有，表示的是：对于半径不同的圆，其各自周长与半径的比为定值，或者说为常数，记该常数为2π，则圆的周长与直径之比为π，当然也是一个常数。好，既然圆的周长和直径之比是一个常数，下一步要做的就是去寻找这个常数或它的近似值了。

现在我们来学习一下刘徽的割圆术计算π。

如图24-2所示，设单位圆半径OP=OR=r=1，△OPR是内接正六边形中的一个正三角形。PR是内接正六边形边长，PR=a₁=1，PS=b是边长PR之半，也是内接正六边形的弦长之半。OY为∠POR的角平分线，OY垂直平分PR，b=a₁/2，并由内接正六边形求出内接正十二边形。△OPT是内接正十二边形中的一个等腰三角形，其边长为PT=a₂，PR=a₁=1，PS=b，OS=c，ST=d，PT=a₂。

图24-2　由六边形求十二边形

已知：单位圆半径r=1，内接正六边形边长a₁=1、周长S=6、π=3。我们的目标是：由上述已知条件，求a₂。其实质是由六边形求十二边形。

以下是从内接正六边形边长a₁出发，求内接正十二边形边长a₂，并求得内接正十二边形周长S₁₂和圆周率π₁₂的一组公式，且第一轮计算为

S₁₂=12×a₂=6.211 68

π₁₂=S₁₂÷2r=6.211 68/2=3.105 84

这个过程可以无限地循环执行下去，把圆分割成12，24，48，…，3×2n，我们把过程抽象一下。

假如上一次我们已经计算出第n-1次迭代得出的弦长为yn₋₁，第n次迭代得出的弦长yn（见图24-3），则

图24-3　过程抽象

把刚才的过程写成Python代码如下：

作业：把割圆术求π的过程，做成GIF动图。

25　同班同学中生日相同的可能性有多大

【本章摘要】

数学内容：概率，互补事件的概率。

编程内容：matplotlib画图。

大多数人在自己同学中发现有人和自己生日相同时，都会觉得很吃惊，其实如果我们认真地算一算，就会发现原来这也不算一件很稀奇的事。

25.1　与特定的人生日相同的概率

我们的生日都在一年中的某一天，从1月1日到12月31日中的某一天都很正常，不会有某一天出生的孩子特别多或少（当然闰年的2月29日除外）。

因此，一个人的生日的概率便是1/365。如果要求某个学生和班主任生日相同的概率便是1/365，这个数很小，发生这样的事情的可能性不大。

在一个30人的班级里，计算有人和班主任生日相同的概率只要将30人的概率相加即可，也就是30/365，结果约为0.082，这个概率也很低。

25.2　30人中有人生日相同的概率

这次不是计算与特定的一个人生日相同的概率，而是计算在30个学生中间存在生日相同的学生的概率。在这种情况中，我们也可以利用计算互补事件的概率的方法，“30人中有人生日相同”的互补事件就是“30人中没有人生日相同”，这个概率的计算方法如下。

我们将30人排成一列来考虑，第一个学生的生日在何时都无所谓，第二个学生的生日不能和第一个相同，因此只要和第一个学生的生日不同即可，概率为364/365；第三个学生与第二个、第一个学生的生日都不相同的概率为363/365。将这些都综合考虑，那么三个学生生日都不相同的概率为

因此，30个人生日都不相同的概率为

这样的式子应该不会有人用手去算吧，用Python编程算一下即可，结果为0.29，因此30人中有人生日相同的概率就是1-0.29，约为71%。也就是说，如果我们调查10个班级，会有7个班级中有同学生日相同，想不到吧？

顺便，我们用一个图表计算一下班级中的人数和有人生日相同的概率之间的关系，水平方向表示班级人数，垂直方向表示对应的概率，如图25-1所示。

图25-1　班级人数和有人生日相同的概率之间的关系

代码如下：

这个曲线是否让你觉得很吃惊？

26　布丰投针求π

【本章摘要】

数学内容：概率，三角函数sin和cos的定义（可以提前介绍），均匀分布的随机数。

编程内容：布丰投针实验安排。

布丰（1707—1788），法国博物学家、作家、数学家（见图26-1）。他生于孟巴尔城一个律师家庭，原名乔治·路易·勒克来克，因继承关系，改姓德·布丰。他从小受教会教育，爱好自然科学，1739年起担任法国皇家花园（植物园）主任。他用毕生精力经营皇家花园，并用40年时间写成36卷《自然史》。他是人文主义思想的继承者和宣传者，在作品中常用人性化的笔触描摹动物，其作品《马》《松鼠》都入选了人教版中小学语文教材。相比之下，他的另一本著作《或然性的算术实验》（见图26-2）虽然不为大众所知，但却是第一个把微积分应用于概率论的名著。他设计的投针实验，在统计学领域广为人知，这个实验因为太出名，后人专门把它命名为“布丰投针实验”（Buffon's Needle）。

这个实验步骤是这样的：

（1）取一张白纸，在上面画上许多条间距为a的平行线。

（2）取一根长度为l（l≤a）的针，随机地向画有平行直线的纸上掷n次，观察针与直线相交的次数，记为m。

图26-1　布丰

图26-2　《或然性的算术实验》

（3）布丰用微积分证明了针与直线相交的概率是

p=2l/πa

他反过来根据这个公式设计了通过投针实验模拟计算圆周率。这在1777年是个异想天开令人费解的实验，一百多年后，随着计算机的出现，人们在布丰投针思想的启发下发展了蒙特卡洛模拟方法，这个方法广泛应用于原子能、固体物理、化学、生态学等学科研究中。

我们在前面章节学习过经典的割圆术求π，也学习过奇妙的用撒豆子求π的方法，这些方法讲清楚道理都比较好懂，布丰投针实验从安排上就很古怪，很难懂。我们试着一步步来理解一下其背后的道理。

找一根铁丝弯成一个圆圈，使其直径恰恰等于平行线间的距离d。可以想象得到，对于这样的圆圈来说，不管怎么扔下，都将和平行线有两个交点。因此，如果圆圈扔下的次数为n次，那么相交的交点总数必为2n。设想把圆圈拉直，变成一条长为πd的铁丝。显然，这样的铁丝扔下时与平行线相交的情形要比圆圈复杂些，可能有4个交点，3个交点，2个交点，1个交点，甚至于都不相交。由于圆圈和直线的长度同为πd，根据机会均等的原理，当它们投掷次数较多且相等时，两者与平行线组交点的总数应该也是一样的。这就是说，当长为πd的铁丝扔下n次时，与平行线相交的交点总数应大致为2n。

转而讨论铁丝长为l的情形。当投掷次数n增大时，这种铁丝跟平行线相交的最大的交点总数m应当与长度l成正比，因而有：m=kl，式中k是比例系数。

为了求出k来，注意到l=πd时的特殊情形，有m=2n。于是求得

代入前式就有

从而得到

将此结论推广到l=a/2，那么最多也只有一个交点，m与n的比值是针与直线相交的概率。此证明虽不是太严谨，比如我们在证明中就假定圆和直线与平行线相交的期望相等，铁丝的长度与平行线的交点个数成正比等都没有证明，但做这样的理解是没有问题的。

由于向桌面投针是随机的，所以假如用二维随机变量（X，θ）来确定它在桌上的具体位置。设X表示针的中点到最近平行线的距离，θ表示针与水平线的夹角，如图26-3所示，假定针长为2，平行线之间的距离也为2，针的中点为O，那么，

X∈[0，1]，θ∈[0，π/2]

图26-3　投针实验

且X和θ在其取值范围内均匀分布，即在其区域内取每一个值的概率都一样，那么容易得到，当

时，针与直线相交；否则，针与直线不相交。

根据这个公式，我们可以用计算机来模拟布丰投针实验求圆周率，Python代码如下：

27　从2ab≤a²+b²几何意义说起

【本章摘要】

数学内容：均值不等式的几何含义。

编程内容：GIF动图实现均值不等式的变化过程。

有一些数学公式具有明确的几何含义，当理解了这些几何含义以后，我们再看到这个公式的时候，脑海里会浮现这个数学图形，公式就变得生动了。我们来看几个例子，比如常见的2ab≤a²+b²这个公式，我们来分析一下其几何含义。

假如设a²+b²=c²保持不变情况下，我们知道这实际上是一个直角三角形或是半圆内的一个直角三角形，圆的直径是c，如图27-1所示，直角△AMB两边分别是a和b，随着直角顶点M在圆上运动，直角三角形的边长在变化，圆的直径不变，就是图27-1中的直径为AB的圆，而2ab就是直角三角形△AMB连续对折构成的菱形ACDB的面积，C点实际上在以A为圆心、c为半径的圆上运动，当C点运动到C'时，菱形变成正方形，此时菱形的面积最大，为c²=a²+b²=2ab。

至此，可以看出2ab≤a²+b²的几何含义是边长为c的菱形（满足a²+b²=c²）中正方形的面积最大，这个过程可以用一个动图（见图27-2）表示出来，代码如下：

图27-1　2ab≤a²+b²的几何意义

图27-2　图27-1的动图

不等式动图

27.1　均值不等式的几何含义

在初中阶段，我们会学习几个基本的不等式，比如：

（1）2ab≤a²+b²

（2）

（3）

（4）

（5）

其实这几个不等式都可以从二元均值不等式变形得到，二元均值不等式就是如下几个值构成的不等式：

这几个值分别定义为调和均值（用H表示），几何平均值（用G表示），算术平均值（用A表示），平方平均值（用Q表示），当且仅当a=b时等号成立。

我们以两个数a和b为例来研究一下，见图27-3，其中a和b构成一条线段，相连的点为S，CS垂直于a，b所在直线，a和b拼接构成的线段的中点为O，以O为圆心，以（a+b）/2为半径的圆交CS于C，SP⊥OC。图中已经分别标注了A，H，G，Q，容易知道OS=（a-b）/2，因此Q=，A和Q的值都很直观。G需要稍微解释一下，我们在第18章学习过如何把矩形变成正方形，我们知道

图27-3中，圆的半径是，也就是A，它等于直角三角形△OSC的斜边OC，另一个直角边，因此。

H有点费解，从S点向OC作垂线交于P点，

这个证明也不难，利用△OSC∽△SPC，可知

代入可以求得CP=H。

图27-3　平均数的几何表示

平均数动图

从图27-3上也可以直观看出H≤G≤A≤Q，当a=b时，这几个值相等。需要提醒的是，当数的个数增加到任意数目时，这几个平均数之间的大小的关系仍然成立。我们接下来通过Python语言实现在a+b为常数情况下，随着S点的移动，a，b的大小发生变化，H<G<A<Q的动态变化情况。

通过观察这个动图的变化，读者应该可以发现如下特点：几何平均数和调和平均数要比算术平均低不少，这两种平均数对较小的数字而不是较大的数字更敏感。

除了上面提到的几何解释以外，我们在初中不等式的学习中，还经常用到以下两个结论，其实其几何含义也非常直观，比如：

（1）和定积最大。

即当a+b=S一定时，，且当a=b时取等号，这个不等式的几何含义就是图27-2中的G从右边往中间移动，移动到a=b时，G最大。

（2）积定和最小。

当ab=P一定时，，且当a=b时取等号。这个不等式的几何含义也非常清楚，见图27-3。

在以直径AB为圆的情况下，如图27-4所示，假设AO=a，BO=b，则ab=d²，这是直角三角形的基本定理，利用相似三角形立即可以证明。当d一定，a，b可变情况下，就是说只要保持A点在AB线上运动到A'点后，∠A'CB'仍然是直角，则d仍然不变，而a+b=A'B'长度已经变了。a=b的情况下，圆的直径最小，生活中的一个例子就是假如C点有一盏灯，灯的罩子呈90°开口，当你固定高度，垂直直射覆盖范围最小，而调整照射角度偏一点，覆盖的范围就变大，偏的角度越大，覆盖范围越大。

生成这个过程的动图的程序留给读者朋友了。

图27-4　的几何意义

27.2　利用基本不等式求解最值

例子：求在（0，+∞）的最小值

解：由基本不等式可得

当，即时取等号。

答：当时，f（x）在（0，+∞）有最小值。

练习

1．求的极大值。思路：求1/y的极小值。

2．设a，b为正实数，求的最小值。思路：换元法，把分母简化。

3．已知正数x+y+z=1，证明：。

解法：把（x+y+z）乘上，然后整理一下，把互为倒数的凑一组，然后利用就可以了。

4．已知x²+y²+z²=7，求x+3y+5z的最大值（应用柯西不等式）。

28　“小皇帝”詹姆斯和“篮球之神”乔丹谁更厉害

【本章摘要】

数学内容：篮球运动员的评估方法。

编程内容：篮球比赛投篮的图形化方法，Pandas.DataFrame的使用方法。

如果你喜欢篮球，你肯定知道勒布朗·詹姆斯，你一定会为他全面的技术倾倒，他能突、能投、能传、能盖，在篮球场上几乎无所不能，但是，你知道吗，“篮球之神”乔丹可以让他一个三分球，为什么这么说？请看表28-1。

表28-1　NBA所有职业球员的常规赛效率值排名前4位

表28-1是NBA所有职业球员的常规赛效率值排名前4位，乔丹以27.9高居第一，他在38.3分钟里面得分30.1，而詹姆斯在38.4分钟里面得分27.1，乔丹刚好比詹姆斯强一个三分球。当然，詹姆斯还处在巅峰期，很有可能当他退役时，他的统计数据会比乔丹更出色，毕竟詹姆斯那么强壮，他和乔丹的效率值又那么接近。

现在如果我们想研究詹姆斯攻击点有没有规律，是习惯于从左边攻击还是右边攻击，你会怎么做？他最有可能的出手点会是在哪里？我们首先想到的方法是把他的投篮位置在篮球场上标出来，然后从一个时间累积的全局的角度看看他出手投篮的点的分布。

图28-1是目前为止詹姆斯出手投篮的位置分布图，除了篮下明显是热点以外，他走位飘忽，攻击点覆盖前场，你不知道他会在哪里出手攻击，不可预测正是顶尖高手的一个典型特征。图中红点是命中，灰点意味着未命中，总体来说，詹姆斯一出手就有五成把握命中，这是很恐怖的！

从图28-1中，我们看到了詹姆斯的出手点，那么与NBA的其他选手对比，他的出手效率如何呢？图28-2是詹姆斯在不同区域的投篮命中率，可以看到他在篮下的出手有七成把握命中，这效率堪比顶级中锋了；而在中投区、罚球线，一直往外推到三分线外，他的攻击力不减，多么恐怖！

图28-2中六边形越大，出手频率越高，红色越深代表效率超出球员的平均效率，蓝色代表效率没有达到NBA球员的平均水平。从图中我们看出，詹姆斯无愧“油漆区王者”的称号，出手频率很高，效率也远超一般内线球员。

图28-1　詹姆斯投篮出手点分布图

图28-2　詹姆斯出手效率图

看到这里，你是否觉得这些图真漂亮，它是怎么画出来的呢？用Python能画吗？当然！我们现在就来看看用Python如何画出这么漂亮的图来。

在画图前，我们必须明白图形化是数据的表现形式之一，要想对数据进行图形化的展示，首先要有数据，NBA的统计数据可以免费访问公开网站（不知道CBA是否有这个接口？如果有的话，可以自行用CBA数据替换NBA数据）。例如，通过https://www.basketball-reference.com/网站可以查到完整的NBA比赛统计数据，有一些对NBA感兴趣的人已经在这个网站的接口基础上开发了一个Python模块，通过接口可以获取NBA的统计数据，我们可以直接用以下命令安装：

     pip install basketball-reference-scraper

此外，还有一个群体也已经开发了另一个模块，可以直接把投篮数据叠加到篮球场上，即图形化投篮数据，这个模块叫nbashot，安装方式如下：

     pip install nbashots

联合使用这两个模块，我们就可以获取NBA统计数据，并按我们想要的方式来展示数据。

下面的代码能获取多伦多猛龙队和波士顿凯尔特人队在2019年12月28日这天的比赛的投篮信息，并把多伦多猛龙队的投篮点（见图28-3）在篮球场上标出来，绿色表示命中，红色×表示为未命中，从图上可以看到多伦多猛龙队的右路进攻效率很高，而左路几乎全是打铁。

图中的代码有一些需要进行说明。

第12行，get_shot_chart（），这个函数要求3个参数：“日期”“队名1”“队名2”，返回的是一个字典数据结构（如果大家不记得字典结构了，可以翻看8.3节），字典的两个索引分别是“队名1”和“队名2”，数据分为六类，包括投篮时的坐标（x，y），投篮发生在第几节（QUARTER），是否命中（MAKE_MISS），获得几分（VALUE），离篮筐距离（DISTANCE）。这些数据是以Pandas模块中定义的DataFrame这种数据结构存放的。

图28-3　多伦多猛龙队的投篮点

Pandas.DataFrame对象可以看成一个数据表，表的每一列分别表示不同的属性，行表示不同的数据项。例如，为了表示一个班上不同学生的各科分数，就可以创建一个DataFrame来表示，某班有四个学生，分别叫emma、shannon、mike、may，她们三门课math、science、chinese的某次考试成绩就可以用如下定义的一个DataFrame来表示：

程序举例说明了如何获得DataFrame中的行和列，下面是这个程序执行的结果：

DataFrame（）的第一个参数就是一个二维数组，它的行要与index个数对应，它的列数据可以通过columns中包含的字串为索引进行访问。

再回到图28-4的代码，get_shot_chart（）返回的就是一个用TOR、BOS索引的字典，字典里面包含两个DataFrame对象。可以通过d.get（"TOR"）获取字典中索引TOR对应的一个Pandas.DataFrame对象，再对应地用d.get（"TOR"）.MAKE_MISS或d.get（"TOR"）.x方式获取，其中每一列的数据，我们可以在第13行加一个print（d），把d的数据结构打印出来。

图28-4　get_shot_chart（）返回的数据结构

从图28-5可以看出，TOR所索引的DataFrame包含了x、y、QUARTER、MAKE_MISS等几个列，根据前面的例子可知，可以通过“d.get（"TOR"）.MAKE_MISS”方式访问到。第15～17行，分别展示了怎么获取到某一列的全部数据。同时也可以看到，x，y索引的整列其实是一个字符串列表，表示球员投篮时在x方向和y方向的位置（单位是英尺），比如猛龙队的第一节首次投篮数据就是在x方向23.5英尺，y方向16.3英尺出手，命中（MAKE）了2分。

图28-5　TOR索引的DataFrame

正因为x方向的位置本身不是一个数字，而是一个带有单位ft的字符串，如"23.5 ft"，因此我们需要在26行进行split（），split会把形如"23.5 ft"的字串用空格分成两个字串，并放进一个数组里面，如["23.5"，"ft"]，那么访问"23.5"这个字串就要用split（）[0]来进行，取出来之后，再用float（）转成程序需要的带小数点的数字（即浮点数，英文float）。这就是第26行的意思。

第26行madeshot_x.append（float（d_x[i].split（）[0]））

第31行np.multiply（10，missshot_x）-250，np.multiply（10，missshot_y）-39，这个-250和-39的坐标平移的操作，是因为原点在篮框和篮板相切的地方，在y方向上离底线有3.9英尺的距离，扩大十倍就是39英尺。在X方向上有25英尺的偏移，扩大10倍，即需要减去250英尺。

29　未婚妻问题和神奇的常数e

【本章摘要】

数学内容：概率的基础知识；复利的计算；设A，B是两个事件，如果满足等式P（A∩B）=P（AB）=P（A）P（B），则称事件A，B相互独立；极限的概念。

编程内容：list及其方法的使用，print（）格式化输出。

29.1　未婚妻问题

据政府有关部门统计，中国目前大龄女青年数量在3800万左右。大龄女青年找不到合适对象的一个原因大概就是她们在碰到向她们表白的男孩的时候，想着再等一等，下一个可能更好，而这种心态造成那个最好的男孩向她们表白时，她不知道那竟会是她爱情道路上的巅峰；第二个原因可能是她们不知道在1949年一位美国数学家梅里尔（Merill M. Flood，1908—1991，博弈论数学家）就研究了这个“未婚妻问题”，并且在理论上给出了最优化找对象的建议。由于梅里尔是个男性，出于学以致用的考虑，他是这么假设的：

假设把一生中会向小伙子表示好感的所有女孩放到一个集合A中，总数为N，她们随机排成一个有序数组，按次序先后向他表白，一旦被拒绝，女孩就会决绝离去，应该采取怎样的策略，才能选中最好的那一个女孩呢？

一般情况下小伙会先交往s个女孩（但不会选择这s个女孩中的任何一个为自己的未婚妻），了解自己想要什么类型的女孩，从第s+1个开始，一旦发现当前这一个比之前所有的都更优秀，就选她为未婚妻，那s取多大最优呢？用数学的语言描述就是，在N确定时，当s等于何值时，按照上述方法取得最优元素的概率最大？

根据我们已经学过的概率知识，容易知道第k次选中最佳女孩的概率应为

Pr（k）=P（第k个女孩是最佳）*P（前k-1次测试中最佳女孩在前s中）

为什么要“前k-1次测试中最佳女孩在前s中”？因为如果不是这个情况，按照我们设定的规则，从第s+1个开始，碰到任何比前面所有的女孩都好的女孩就果断选择她，那么到不了第k次，最佳女孩就已经确定了；因此，

概率P（前k-1次测试中最佳女孩在前s中）=；

概率P（第k个女孩是最佳）=；

很显然这两个事件是相互独立的，所以。

那么把k比s大（s+1）≤k≤N的所有情况下的概率都相加，就构成了在选定s的情况下，男孩能找到最佳女孩的概率：

注意最后一项的分母是N-1，因为这种情况就意味着最优的女孩就是第N个，而前N-1个女孩中，最好的在前s个中，可以对为什么第一项的分母是s做类似的理解。上面的公式如果不好理解，我们先来看一个具体的例子。

以四个女孩为例，看看情形如何。我们以1，2，3，4表示女孩优秀的等级，4最优秀，1最差，表29-1表示她们的出场顺序。

表29-1　女孩出场顺序

根据排列的算法，共有24种出场可能，每种出场有同样的概率。

按照上述的规则，若s=0，表示一有女孩表示好感，男孩就立即选她做未婚妻，那么有6种排列法小伙子可以找到最优女孩，即第四列的全部，因此小伙子找到最佳女孩的概率是。若s=1，则凡打“*”号的出场顺序，男孩都可找到最佳女孩，其概率是。若s=2，则凡打“〇”号的出场顺序，男孩都可找到最佳女孩，其概率是。若s=3，则凡打“Y”号的出场顺序，男孩都可找到最佳女孩，其概率是。

由此可见最好的方法是取s=1，也就是先交往一个女孩，从第二个开始碰到比前面都好的就立即选她做未婚妻。

我们用程序来模拟一下，当N越来越大的情况下，应该从第几个开始呢？

该程序的输出是：

当N趋近无穷大的情况下，最佳，，也就是说，如果一个人预计一生中共有求爱者N人，最好的办法就是先和前N/e人练习恋爱，静候下一个比这些人都好的人，由于1/e≈37%，因此这条爱情大法也称为37%法则。不过，37%法则有一个小问题：如果最佳人选本来就在这37%的人里面，错过这37%的人之后，她或他就再也碰不上更好的了，但其本人并不知道最佳人选已经被拒，而会一直痴痴地等待，最后成为大龄青年。

上面这个极限公式的推导需要用到高等数学的知识，我们不去管它，我们的程序已经证明了N足够大时，结果确实就和1/e非常接近，我们选择直接相信它。但这个公式里出现了一个e，将来找对象也和它有关系，我们有必要来学习一下。

29.2　神奇的常数e

自然常数e是一个无理数常量，约等于2.71828，由瑞士大数学家莱昂那多·欧拉（Leonhard Euler，1707—1783）命名。但实际上，第一个发现这个常数的并非欧拉本人，而是雅可比·伯努利（Jacob Bernoulli），约翰·伯努利的亲哥哥。这个数不像π那样，看到一个圆形物体我们就会想起它，但其重要性丝毫不亚于π，其原因是它和自然界的增长模型息息相关。

我们先来看一个直观的例子，假设你往银行存了1元钱，银行按年记利息，利率100%，那么一年后变成了2元，因为利息1块加本金1块，比原来的1块就是2倍。现在由于激烈的竞争，有银行同业比较勤快，不是一年计息一次，而是半年计息一次，而且允许下一次计算的时候可以把上一次产生的利息加进去作为本金计息，当然银行改成半年计息一次后，利率也相应调低，调低的规则是计息期缩短多少倍，利率就缩小多少倍，也就是说，计息变成半年后，利率缩减至50%。这种情况下一年后的收益增长是多少倍呢？简单按照复利公式就知道是倍。这样缩短计息周期，虽然每周期的增长率调低了，但是实际收益还增加了；那么再继续往下做一个推演，现在假设再把计息期缩短一点，变成4个月计息一次，利率相应缩减为33.333 33…%，这种情况一年后的增长倍数是多少？继续套用复利公式：，相比上一次2.25，又增加了。如果这种竞争继续下去，银行不断缩短计息周期，银行是不是就破产了呢？让我们用Python来计算一下，看看究竟会发生什么？以下代码计算了计息周期从一年一期到1/100年一期后，一年后1块钱究竟增长了多少倍，如图29-1所示。

图29-1　复利计算

可以看出的结论是，随着计息期切分得越短，你的资产增长倍数会越来越大。但是并不会无限大，而是趋于收敛（往后走线条几乎就停滞了）。这个收敛的值就是2.718 281 828 459…无限长，也就是自然常数e。所以按照以上的计息期的控制方法，银行就算把计息期缩减到无限短，到年底你也最多能得到e倍的原始本金，这就到顶了，不会再多了。这就是一个最直观的对自然常数的理解。虽然不像圆周率等于周长比直径那么直观，但其重要性却不亚于圆周率。上面计算的过程，如果用极限的表达式来表示就是

其含义就是，n趋向于无穷大的极限为e。

虽然正常的银行不会推出连续复利这种优惠政策，但在自然界中，大多数事物都处在一种“无意识的连续增长”状态中。对于一个连续增长的事物，如果单位时间的增长率为100%，那么经过一个单位时间后，其将变成原来的e倍。而生物的生长与繁殖过程，恰恰也类似于“利滚利”的过程。

30　胡不归问题的动图实现

【本章摘要】

数学内容：胡不归问题及其建模。

编程内容：胡不归问题的GIF动图实现方法。

如图30-1所示，一条笔直的公路穿过草原，公路边有一消防站A，距离公路垂直距离hkm的地方有一居民点B，与公路的垂直交点为C，AC的直线距离是c km。一天，居民点B着火，消防员受命欲前往救火。若消防车在公路上的最快速度是a km/h，而在草地上的最快速度是b km/h，则消防车在出发后最快经过多久可到达居民点B。（注意：消防车可从公路的任意位置进入草地行驶。）

图30-1　胡不归问题

这个题目就是著名的胡不归问题，曾经是非常著名的“难题”。近年来陆续成为各地中考模拟题的热门考点，今天我们来彻底研究研究。

我们先来对这个问题建立一个数学模型，问题是要解决在哪个地方开始进入草地所费时间最少。假设我们从D点开始进入草地，设AD=x，那么在AD上消耗的时间是x/a，在DB上消耗的时间则为DB/b，容易得到需要的时间t为

这个问题有多种初等解法，我们今天讲解容易理解的代数解法和几何解法。我们先来学习一下代数解法：

解法1

思路：t一定在从A到C的某个点上会有一个最小值，那么也可以把t看成一个常数，去找到x的解。因此可以把这个方程变形一下，来研究一下x的解存在的条件和根。特别需要强调的是这个思路对很多类似的问题都成立，不仅局限于胡不归问题。

把这个方程化简成关于x的方程得到

（b²-a²）x²+2a（ac-b²t）x+a²b²t²-a²（c²+h²）=0

由根的判别式得到

[2a（ac-b²t）]²-4（b²-a²）（a²b²t²-a²（c²+h²））≥0

[（ac-b²t）]²-（b²-a²）（b²t²-（c²+h²））≥0

a²b²t²-2acb²t+a²c²+（c²+h²）（b²-a²）≥0

（abt-bc）²≥h²（a²-b²）

得到，只可能存在两种情况：

（1）

（2）

第一种情况，因为，明显不合理，忽略，那么只能是第二种情况：

t的最小值就是，对应的。

解法2

要求的时间是，对式子变形处理，得到，因为a，b为定值，所求问题就转化为求的最值，因此需要在图中构造出长度为的替换线段。我们在直线AC下方作一条射线AM，使得，过点D作DE⊥AB，则，因此，问题转化为求的最小值，显然垂线端最短，即过点B作AM的垂线，与AC的交点D₁即为转折点。

那么我们知道：△BCD₁∽△AD₁E₁

容易求得：

那么，，

因此总的时间为

几何解法直观而且容易理解。

接下来，我们用Python实现一个动图表示这个变化的过程。

胡不归问题动图

31　另一个角度认识三角形和面积

【本章摘要】

数学内容：本章以单位菱形的面积公式的调整系数作为正弦的定义为起点，统一用面积法引入了斜高公式、四边形的面积公式、正弦定理、余弦定理，并结合估算月亮和地球之间的距离历史故事讲解余弦的具体应用。

编程内容：等时曲线的动图模拟。

在中国的一二线城市，土地已经到了寸土寸金的地步。如果给定一块三角形的地，让你计量面积，你打算如何量呢？你是否脑子里立即想到了三角形面积等于底乘以高再除以2，因此测量一条底边和它对应的高就行了。这个方法虽不能算错，但是在执行时，你如何准确找到三角形的另一个顶点到这条边上对应的垂直线呢？如果找到的垂点位置哪怕偏离一点点，面积就被放大了很多，意味着损失了很大一笔钱。因为这个原因，在实际工程测量工作中，几乎没人用底高公式，用得最多的是如下的海伦公式。

假设在平面内，一个三角形的边长分别为a、b、c，三角形的面积S可由以下公式求得：

式中，p为半周长（周长的1/2），。

当应用海伦公式时，我们只要测量三条边长，简单又精确。那么除了海伦公式还有没有别的三角形面积公式呢？

矩形面积等于长乘宽，即S=wh，这个公式很容易从图31-1（a）中直观得到。设想图31-1是用木条和钉子做成的框架，一不小心，框架变斜了[图31-1（b）]，于是矩形变成了平行四边形，小正方形变成了小菱形，假定小菱形的面积是Sα，则菱形面积公式就变成了如下形式2×2×Sα：图中的Sα代表一个边长为1、夹角为α的小菱形的面积，很明显这个面积和夹角α直接相关，α=90°时，菱形变成正方形，面积为1；α为0°

或180°时，面积为零。如果我们用一个系数（sinα）来衡量这种正方形被压扁的程度，那可以定义边长为1，有一个夹角为α的小菱形的面积Sα：

Sα=1×1×sinα

图31-1　矩形变成平行四边形

sinα叫角α的正弦。sinα具有如下性质：

（1）当α为0°或180°时，sinα=0。

（2）当α=90°，sin90°=1，菱形变成正方形，面积为1。

（3）sinα=sin（180°-α），因为二者指向同一个小菱形。

（4）sinα是一个无量纲的数，它只和角张开的大小有关。

31.1　平行四边形面积公式

若平行四边形ABCD中∠A=α，AB=a，AD=b，应用前面的定义，则平行四边形的面积为

S□ABCD=absinα

如果设△ABC角A，B，C所对的边长分别为a，b，c，因为三角形面积是平行四边形的1/2，所以有

上面每一项都除以abc/2，则立即得到

这就是著名的正弦定理（The Law of Sines），见图31-2。

图31-2　正弦定理

这个面积公式的形式是用两边夹一角的形式来表示的，三角形面积公式还可以用底乘高来表示，有

因为

所以

sinA=h/b

sinA=h/b，这个定义是通常教材上采用的正弦的定义，与前面面积系数的定义不同，但本质上一样的。同样我们可以得到

sin∠ACP=sin（90°-A）=AP/b

由于勾股定理，我们还可以得到

sinA²+sin（90°-A）²=1

由于A和∠ACP互为余角，所以又重新定义角A的余弦：

cosA=sin（90°-A）=AP/b

从斜边永远大于直角边，可以得到无论正弦还是余弦，都永远小于1，我们把正余弦的定义放到平面坐标系里来看，其符号如表31-1所示。

表31-1

由于四个象限里面（x，y）的符号有正有负，在半径为R的圆上，每个点的坐标用正、余弦的定义来看，都可以表示为（rcosα，rsinα），见图31-3。

图31-3　正、余弦定义

31.2　斜高公式

在图31-4的△ABC中，设AB=c，在直线AB上任取一点P，设CP=b*，∠CPB=A*，则△ABC的面积可以写成

这个公式叫斜高公式。它的证明很简单，利用

△ABC=△APC+△BCP

然后直接应用前面的两边夹一角的公式定义就可以得到斜高公式。

图31-4　斜高公式

至此，我们已知有四种求三角形的面积公式：

（1）海伦公式；

（2）；

（3）；

（4）斜高公式。

如果应用前面sinA=h/b的结论，实际上，b*×sinA*就是在求高，由此可知，公式（2）、公式（3）都是斜高公式的特例。

31.3　任意四边形面积公式

设ABPC是对边互不相交的四边形，对角线BC=a，AP=b*，直线AP和BC的交角及交点都记为C*，则四边形ABPC的面积为

证明：如图31-5，存在一凹一凸两种四边形的情况，对左边的凸四边形，有

凹四边形使用△ABC-△BCP一样可以得到上面的结论。

图31-5　凹凸四边形

31.4　余弦定理

前面我们介绍了正弦的两种定义，一个是用夹角为α的菱形的面积调整系数的方式来定义，另一个是用直角三角形的直角边和斜边的比值来定义，二者等价，彼此可以互相推出。

余弦定理的表述为：

在任意△ABC中，有恒等式

a²=c²+b²-2cbcosA

b²=a²+c²-2accosB

c²=a²+b²-2abcosC

这三个等式只要证明一个就可以了，另两个类似。

在证明余弦定理以前，我们先来理解一下这个公式，比如：

c²=a²+b²-2abcosC

如果C为直角，那么余弦定理就退化成勾股定理；当C变得比直角大时，保持a，b边大小不变，那么c实际上变长了，由于C为钝角，cosC为负数，因此-2abcosC为正，也就是说c为钝角时，以c为边的正方形面积比原来C为直角时大了-2abcosC。当C继续扩大到180°时，cosC=-1，余弦定理就退化成和的平方公式。

接下来，我们来证明余弦定理c²=a²+b²-2abcosC（见图31-6）。在证明之前，我们来分析一下思路，从前面的分析可知，2abcosC是一个面积，它描述的是直角三角形变成钝角或锐角三角形以后三条边构成的正方形之间的面积关系。那么能不能把2abcosC构造出来呢？为了构造cosC，需要分两种情况，即C是锐角或钝角，如果是锐角，只要做出一个和C加起来是90°的角，并且两边分别是a和b，就构出一个三角形，其面积是。有了这个想法以后，我们来看一种证明余弦定理的方法。

图31-6　余弦定理

图31-7（a）对应△ABC角C为钝角情况，图31-7（b）对应角C为锐角情况，把△ABC以C为圆心，旋转90°，A、B点对应的新的位置为A'、B'，由于旋转90°的原因，BC⊥CB'、AC⊥CA'、AB⊥A'B'，对应左图，存在

化简以后就得到

c²=a²+b²-2abcosC

对于右图锐角情况，可以利用

图31-7　余弦定理证明

△BB'A'+△B'A'A=△BB'C-△ACB'+△CA'A-△BCA'

可能你会疑惑为什么会想到要这么做，原因是我们要同时构造a²，b²，abcosC这些项，旋转90°利用了sin90°=1，并且恰好构造了这三个项。从这个角度理解，就会发现旋转90°的巧妙之处。

31.5　正弦定理和余弦定理有什么作用

正弦定理和余弦定理都属于三角学的内容，最早是由航海和天文观察的实际需要而发展出来的数学。希帕克斯是古希腊伟大的天文学家（Hipparchus，公元前190—前120，中国当时处于西汉时期），当时就利用了一次日食估算出了地球和月亮之间的距离，其精度用今天通过激光测量的距离为基准来衡量的话，误差不超过3%，我们来看看他是怎么估算的。我们知道日食和月食是由于日、地、月在运动过程中处于特殊的相对位置而造成的自然现象。地球本身是不发光的，它不停绕日旋转，背向太阳的一边就拖着一条长长的圆锥形黑影，叫地影。月球本身也不发光，它不停地绕地球旋转，并随同地球绕日运转，它的背后也拖着长长的圆锥形月影。当月球运转到日、地之间，并且三者处在或接近于处在同一直线上时，地球上被月影扫过的地区就会看到月球把太阳遮挡住一部分或全部，这就是日食现象。当月球运转到太阳和地球的背后，地球在日、月之间，并且三者也几乎在一条直线上时，月亮被地影遮没了一部分或全部，就发生月食现象，具体示意见图31-8。

图31-8　日食示意图

在公元前200年那个年代（日心说由哥白尼在1514年首次提出），人们还认为地球是宇宙中心，太阳和月亮绕地球转，人们已经知道地球是圆的，也知道了日食和月食背后的道理，也已经建立了赤道和纬度的概念。

大约在公元前129年（根据NASA推测最有可能在这一年），在土耳其的达达尼尔海峡（古称Hellespont）和埃及的亚历山大城（Alexandria）发生了一次日食，我们可以合理推断，著名天文学家希帕克斯早就在等待这次日全食，并早早安排好了学生在今天埃及的亚历山大城和Hellespont同时开展了这次宏伟的观测实验。希帕克斯应该早就计算到在Hellespont能观看到日全食，他唯一需要确定的是这一天在亚历山大城能观看到什么程度的日偏食，一旦这个数据精确测定，他就能估算出月亮和地球之间的距离；当日食如期发生后，来自亚历山大城的消息称当天在亚历山大城太阳最多只被遮盖了1/5。

于是希帕克斯进行了如下估算：

由于在地球上一天之内任意一点观测太阳上边缘和下边缘的角度变化只有0.5°，因此，当从图31-9中B点（Hellespont城）经E点看到D点，也就是Hellespont看到日全食时，A点（亚历山大城）经E点看到C点，即太阳的1/5被遮挡时，这两条线的夹角基本就是0.1°，也就是图中的α=0.1°。注意：为了读者容易理解，月亮被画在地球和太阳中间的位置，但实际上古希腊时期，人们就已经知道月亮和地球之间的距离远远小于地球和太阳之间的距离（大约是1/389，当时不知道这么精确，但已经知道大得非常多）。当时人们已经知道，A点和B点这两城的纬度差异是9°，假如地球的半径是r，那么根据AB距离等于

AB=（2πr）×（9/360）

图31-9　地月距离估算（一）

同时，由于AB也在另一个以月亮为圆心、地球和月亮距离R为半径的大圆上，因此，

AB=（2πR）×（0.1/360）

这样能粗略推出R=90r，但是希帕克斯很快就意识到这样估算偏差是较大的，因为R如果比r大这么多，大圆上观测到的距离应该是图31-10中的AF而不是AB，而AB⊥OB，因此∠ABF=50°

所以

AF=ABsin50°=ABcos40°=0.766AB

因为

AF=（2πR/360）×0.1

图31-10　地月距离估算（二）

所以

AF=0.766AB=0.766×（2πr/360）×9

所以

R/r=90×0.766=69

后来希帕克斯又进一步根据他观测到的地球旋转轴的摆动，进一步修正了月亮和地球的距离应介于62r<R<73r。而我们今天知道，地球和月亮之间的平均距离是60r，考虑到那时人们还不知道地球其实不是标准的球体，人们对天体的观测还很初级，能做到这个精确度真的是叹为观止了。

31.6　等时曲线的动图模拟

在数学中，摆线（Cycloid）定义为，一个圆沿一条直线运动时，圆边界上一定点所形成的轨迹。它是一般旋轮线的一种，摆线也是最速降曲线问题和等时降落问题的解。那么什么是最速降曲线呢？

在一个斜面上，摆两条轨道，一条是直线，一条是曲线，起点高度以及终点高度都相同。两个质量、大小一样的小球同时从起点向下滑落，曲线的小球反而先到终点。这是由于曲线轨道上的小球先到达最高速度，然后以非常高的速度通过平缓的曲线部分，所以先到达。然而，两点之间的直线只有一条，曲线却有无数条，那么，哪一条才是最快的呢？伽利略于1630年提出了这个问题，当时他认为这条线应该是一条弧线，可是后来人们发现这个答案是错误的。

1696年，瑞士数学家约翰·伯努利首先解决了这个问题，并得意扬扬地拿这个问题向其他数学家发起了公开挑战。

我们来欣赏一下他的挑战信：

我，约翰·伯努利，向世界上最杰出的数学家宣布。对聪明人来说，最吸引人的莫过于解决一个诚实而富有挑战性的问题，并享有随之而来的名声和永恒的纪念。就像帕斯卡、费马等人一样，我希望通过向我们这个时代最优秀的数学家提出一个问题，来检验他们的方法和智力的力量，从而赢得整个科学界的赞赏。如果有人向我通报所提出问题的解决办法，我将公开宣布他值得赞扬。

隔着时空，我们都能感觉到伯努利目空一切的表情，他满以为这个挑战会难倒同时代的所有数学家，但是很快他就发现，牛顿、莱布尼兹都很快就找到了正确答案，约翰·伯努利是否就此变得谦虚我们不得而知了。

如图31-11所示，这条最速降曲线就是一条摆线，也叫旋轮线。这个曲线的推导过程需要用到高等数学知识，我们这里就不讲了，直接给出答案：

图31-11　摆线

等时曲线动图

这条曲线还有一个奇妙的性质，就是你把小球从不同起点释放，这些小球都会同时到达终点，所以这个曲线又叫等时曲线。我们接下来就用Python来模拟一下这个过程。

32　什么样的屋顶排水最快

【本章摘要】

数学内容：曲线的参数方程，圆的参数方程，运动的分解，旋轮线的参数方程，圆内旋轮线的参数方程，正弦和余弦函数，角的弧度定义。

编程内容：旋轮线的动图实现，圆内旋轮线的动图实现。

中国古建筑屋顶外形多样，瑰丽多姿，其中最利于排水的就是著名的“悬山顶”（见图32-1）。悬山顶采用的是高屋脊梁、大坡度的设计，靠近屋脊两侧的坡度超过60°，而在屋檐部的坡度不足30°，利用陡坡使雨水急下，再因惯性冲出屋檐外。这种屋顶曲线一度是中国古代建筑师的看家本领，轻易不泄露的技术秘密。这种屋顶首次出现在唐朝，北京故宫中许多房屋的屋顶就采用这种设计，其设计思路就是让雨水尽快从屋顶排到地面，减少雨水渗透到瓦上，保持屋内的干燥。

图32-1　悬山顶

而在西方，1696年（中国的清朝康熙三十五年），瑞士数学家约翰·伯努利首次提出了这样一个问题：“设A和B是平面上高低不同且不在同一铅垂线上的两点，在所有连接A和B的平面曲线中，沿哪一条曲线，使仅受重力作用且初速度为零的质点从A运动到B所需时间最短”，他成功地用严谨的数学分析方法解决了这个问题，后人把这种曲线称为最速降曲线。

悬山顶其实就是遵循这种曲线，除此而外，如果你玩滑板，很多的弧形滑板场也是按这个曲线设计的。

今天我们就来学习一下它到底有什么妙处。

32.1　摆线的轨迹

当人在平地上匀速骑自行车时，自行车水平向前移动，车轮的圆心（也就是车轮的轴承）也向前平移，轮子在匀速旋转。如果轮子的半径是R，在轮子不变形的情况下，轮子转一周，车子前进2πR的距离，任何时候轮子边缘上的一点和圆心的距离都保持R不变，这些是我们生活中的经验；假如以公路为X轴，随着车子的移动，轮子上任意一点的轨迹会是什么样子呢？它又走了多远的距离呢？

如果我们知道圆滚动时某个点的坐标的变化，我们就知道了它的轨迹；而我们知道滚动的圆的圆心的Y坐标一直保持不变，就是R，而X一直在线性增加，而圆上某一个点，由于和圆只有相对转动，如果我们把圆心的运动和这个点的相对转动叠加到一起，就会得到某个点的真实的运动轨迹。这种把一个复杂运动分解成两个简单运动的叠加的方法我们以后会经常用到。

如图32-2所示，假设开始时圆和X轴相切的点为原点，圆向右滚动了角度θ，由于弧长AM=AO，点M的位置由（0，0）变为

图32-2　圆的滚动

有了这个知识铺垫，就可以来画一个滚动圆上任意一点的轨迹的动图了，Python代码如下：

图32-3中的线叫摆线（Cycloid），又称旋轮线。人们在17世纪就发现摆线具有如下性质：

（1）它的长度等于旋转圆直径的4倍，即8r，尤其令人惊讶的是它的长度是一个不依赖于π的有理数。

（2）在弧线下的面积，是旋转圆面积的3倍，即3πr²。

（3）当滚珠从一个摆线形状的容器（本例中的摆线上下翻转一下）的不同点放开时，它们会同时到达底部。

（4）摆线翻转一下就是最速降曲线。

以上几个结论的证明都要用到微积分知识，从略。

图32-3　摆线

摆线轨迹动图

32.2　圆内旋轮线的轨迹

当一个小圆沿着固定大圆的内侧运动时（见图32-4），圆上某点的运动轨迹又会是什么样子呢？

图32-4　小圆沿固定大圆的内侧运动

这个曲线的运动，也仍然要采用运动叠加的方法来研究。首先，我们容易看出，尽管单个点的运动看起来很复杂，但内圆圆心C的轨迹还是一个圆，半径是R-r；其次，内圆在滚动时，圆周上的点和其圆心的距离不变，总是r。如果把作滚动的那个圆称为滚圆，而另一个固定圆称为导圆，并设导圆半径为R，滚圆半径为r，那么圆周上的一个点P（见图32-4）的轨迹线的参数方程就是

这个方程式由点C的坐标叠加上点P相对于点C的偏移量而成。

其中，后一项的角度要特别说明一下为什么是，由于滚圆和导圆之间是无滑滚动，因此滚圆滚过θ后，弧AB长度等于弧长BP，因此有，而，因此点P的坐标就是如上所示的方程式。可以看出，随着R/r的不同，轨迹会有比较大的变化，图32-5分别展示了R/r=4和2的情况。当R=2r时，轨迹成了一条直线，这个性质就可以实现我们前面讲的把旋转运动转化成直线运动的目的。

图32-5　R/r不同时轨迹的变化

圆内旋轮线动图1

圆内旋轮线动图2

练习

如果一个滚圆沿着导圆的外侧滚动，则会形成如图32-6的心形曲线，其参数方程为r=a（1+cosθ），请作出图32-6所示的心形曲线的动图。

图32-6　心形曲线

33　阿氏圆的应用

【本章摘要】

数学内容：阿氏圆和“PA+kPB”型的最值问题。

编程内容：任意区间内均匀分布随机数的生成方法，阿氏圆GIF动图产生方法。

阿氏圆是阿波罗尼斯圆的简称。已知平面上两定点A、B和一动点P，则所有满足PA/PB=k且k≠1的点P的轨迹是一个圆，这个轨迹最先由古希腊数学家阿波罗尼斯（Apollonius，约公元前260—前190）发现，故称阿氏圆，见图33-1。

图33-1　阿氏圆

我们先用纯代数方法来分析一下这个过程。

令B为坐标原点，A的坐标为（a，0），则动点P（x，y）满足

则根据距离公式我们知道

代入上面公式并进行化简会得到

这个公式具有勾股定理的形式。我们前面章节里讲过，如果勾股定理的斜边保持不变，而直角边可变，它的轨迹是一个圆，因此由这个条件能推出所有满足条件的点轨迹是个圆，圆心C的坐标就是，圆的半径就是。

我们来分析一些特例，假如0<k<1，比如取0.5，那么可以算出，圆心C的坐标是（4a/3，0），C在A的右边；如果k>1，比如取2，那么C的坐标是（-a/3，0），C点在B的左边。

因为P是动点，所以当P运动到D点和E点时，仍然保持比例关系不变，因此可以得出：DA/DB=k，EA/EB=k。假设DA=μ，那么DB=μ/k，由于DA+DB=a，可以求得

因此D点坐标为，E点的坐标为。注意我们假定了0<k<1，假如k>1，D，E点的坐标要重新计算。由于CD为圆的半径，因此，

分别代入可以得出

因为

所以

△PAC∽△BPC

所以

∠APC=∠ABP

读者有没有发现，阿氏圆中无论动点如何变，都含有一个母子相似形，△BPC是母，△PAC是子，两个三角形共一边。

假如AB保持不变，而k发生变化，那么圆心位置C和圆的半径会发生变化，但母子三角形相似关系保持不变。

接下来我们写一个程序来验证一下满足这个关系的点是否真的构成一个圆，实验是这么安排的：预先设定两个点的坐标，然后在指定区域内产生随机点P，计算该点和A，B的距离，如果满足PA/PB=k，就打一个点，看看最后留下痕迹的点是否形成一个圆。程序如下：

以上程序产生图33-2所示的动图，证明确实产生了阿波罗尼斯圆。

图33-2　阿波罗尼斯圆实验

阿氏圆实验动图

也可用后面的代码生成阿氏圆动图，如图33-3所示。

图33-3　阿氏圆动图

阿氏圆动图

33.1　阿氏圆在生活中的应用

阿氏圆在实际生活中的一个典型应用就是拦截问题，假设一艘走私船在海上沿一个固定的方向全速航行，速度为Vf，缉私艇发现后，立即准备拦截，其最高速度为Vi，由于其最高速度之比为常数，因此可以成功拦截的点就构成一个阿氏圆。

33.2　阿氏圆在考试中的应用

中考中也经常考阿氏圆，其典型的题型就是一个“PA+k·PB”型的最小值问题。

例如，如图33-4所示，已知∠AOB=90°，OB=4，OA=6，圆C半径为2，P为圆上一动点。

（1）求的最小值。

（2）求的最小值。

解题基本思路：构造△OPC∽△OBP，则（相似比）。

图33-4　阿氏圆典型题型

①分别连接圆心O与系数不为1的线段BP的两端点，即OP，OB；

②计算的值，则；

③计算OC的长度，由得（相似比×半径）；

④连接AC，当A、P、C三点共线时，；

⑤显然A、P、C三点共线时，长度即为最小值。

类似的题型还有很多，重要的一点就是动点在圆上，需要通过转换把系数不一致的线段转换成系数一样的线段，将系数不为1的线段的两端点和圆心相连，然后用阿氏圆定理进行转化，就知道最值出现在什么情况下了。

练习

如图33-5（a）所示，在平面直角坐标系xoy中，半圆O交x轴于A、B（2，0）两点，AD、BC均为半圆O的切线，AD=2，BC=7。

（1）求OD的长；

（2）如图33-5（b）所示，若点P是半圆O上的动点，Q为OD的中点，连接PO、PQ。

图33-5　习题图

①求证：△OPQ∽△ODP；

②是否存在点P，使有最小值，若存在，试求出点P的坐标；若不存在，请说明理由。

34　圆锥体的体积公式究竟怎么推出来的

【本章摘要】

数学内容：圆锥体积公式，自然数的平方和公式。

编程内容：用Python如何画3D圆锥。

小学阶段我们学习圆锥体的体积公式时，是用圆锥形量筒注满水，往圆柱体里倒，恰好倒三筒水后，圆柱体就满了，于是我们得出圆锥体的体积是圆柱体体积的1/3的结论，这个方法固然有说服力，但不是很漂亮，但在小学阶段也只能如此了。

德谟克里特（公元前460—前370）是古希腊的著名哲学家，原子唯物论的创始人之一，他非常博学，以哲学出名，但是数学成就也很高，在公元前400多年时就已经提出了证明圆锥体体积公式的思路，尤德塞斯根据他的思路，最终完全证明了圆锥体的体积公式。我们来看一下德谟克里特的思路。

将圆锥以平行于底面的平面切成许多薄的圆盘，从侧面来看，如图34-1所示，设底面的半径为r，高为h，将h分为n等分，则对应圆盘的高为h/n，对应圆盘的半径则为。

假设圆锥的体积为V，则如果分别以图34-1（a）、（b）两种用圆柱的体积的累加来估计圆锥的体积，可以看到，左边有部分空隙没有计算体积，而右边多计算了部分区域的体积，很明显有如下不等式：

图34-1　切分圆锥

VL<V<VR

其中，

由于上面两个等式中都涉及求一个平方和的问题，这里告诉大家一个诀窍，这个诀窍可以用于解任何一个n次方和的问题：

如果要求n次方和，那么可以利用比n次高一次的相邻两项的差的公式来计算，这样讲还是太抽象，我们来看一个具体的例子，比如如果要求1²+2²+3²+4²+…+n²，那么我们可以利用（n+1）³-n³=3n²+3n+1来递推：

（2）³-1³=3×1²+3×1+1

（3）³-2³=3×2²+3×2+1

︙

（n+1）³-n³=3n²+3n+1

所有等式相加，左边正好可以抵掉，从而得到

（n+1）³-1=3（1²+2²+…+n²）+3（1+2+3+…+n）+n

对上式化简以后，就很容易得到下面的平方和公式：

代入不等式可以得到

由上面公式可见，当n比较大时，即分割得很细时，1/n基本等于0，VL≈VR；当n为无穷大时，有

据此，德谟克里特给出了圆锥的体积公式为。这样不等式两面夹逼的手法我们是否似曾相识？是的，我们前面在介绍阿基米德求圆周率时也用过类似的手法，但是德谟克里特比阿基米德早大约150年，你可以清晰地看到学术思想的继承。

Python编程画3D圆锥体的程序如下（见图34-2）：

图34-2　求圆锥体体积

35　毕达哥拉斯平均数和几何图形

【本章摘要】

数学内容：算术平均、几何平均、调和平均各有什么特点？

编程内容：随机数及随机数的概率分布概念和图形化描述。

弗朗西斯·高尔顿（Sir Francis Galton，1822—1911）于1822年2月出生于英格兰伯明翰一个显赫的银行家家庭，父亲和祖父都是热爱自然的科学家。他是家中第9个孩子，他的外祖父正是达尔文的祖父，因此他与达尔文是表兄弟。他虽然最早是以地理学家的名义而进入科学界，但在1859年表兄达尔文的《物种起源》发表之后他立即成了达尔文学说的信奉者，其科学兴趣也很快转到与生命有关的科学——遗传学领域中。从遗传学史的观点来看，他的主要贡献是人类遗传学，1885年，他提出了一个全然不同的遗传理论——祖先遗传律。他认为，每一个体从其父方接受了大约一半的遗传组成，从其母方接受了另一半。同样的推理应用到祖亲代：一个个体从其祖亲接受了大约1/4的遗传物质，从其曾祖亲那里接受了1/8的遗传物质，以此类推。并从此理论出发创立了优生学。

1875年，高尔顿挑选了7组不同尺寸的豌豆，并说服他在英国的不同地区的朋友每一组种植10粒种子，最后把原始的豌豆种子（父代）与新长的豌豆种子（子代）进行尺寸比较。当结果被绘制出来之后，他发现并非每一个子代与父代一样，尺寸小的豌豆会得到更大的子代，而尺寸大的豌豆会得到较小的子代。他把这一现象称为“返祖”现象（趋向于祖先的某种平均类型），后来又称之为“向平均回归”。一个总体中在某一时期具有某一极端特征（低于或高于总体均值）的个体在未来的某一时期将减弱它的极端性（或者是单个个体或者是整个子代），这一趋势现在被称作“回归”效应。高尔顿对这种现象进行了深入分析，并创立了“回归分析”，所以他还有另一个广为人知的身份——统计学家。

1906年，高尔顿去参加年度西英格兰家畜展，这个展览的一个大戏就是猜重量竞赛，人们猜测一头公牛的重量，猜得最准的人将获得大奖。高尔顿对自己的“回归分析”理论非常自信，他根本不曾瞧过那头牛一眼，他只是把猜测者的结果加起来，算了一下猜测的平均数（而不是当时统计学家常用的中位数）得出结果是1197磅，并因为和真实重量1198磅只有一磅之差而捧得大奖。

今天我们就讲讲高尔顿运用的数学工具——平均数的几种计算方法。

平均数，我们在日常生活中经常用到，比如我们在对两个班的单科成绩进行比较时，会把所有同学的某科得分全部相加然后除以总人数，这样得到平均得分，据此来衡量这个班的该科成绩的好坏，这几乎成了大家的直觉。每个人都觉得这应该是最公平的方法了，你是否想过为什么？还有没有别的方法？

一般来说，假设已给n个数a₁，a₂，…，an，定义，我们把A称为a₁，a₂，…，an的算术平均（Arithmetic mean），为什么我们以这个算术平均来反映某个整体的情况呢？原来这里面有一个深刻的道理。当我们定义一组数，a₁，a₂，…，an，并希望用一个数x来反映这组数的整体情况时，我们总是希望x和这n个数的偏差在整体上尽可能小。我们知道在平面坐标中，两点的距离可以用坐标差的平方和衡量，假设定义

D=（x-a₁）²+（x-a₂）²+（x-a₃）²+…+（x-an）²

那么我们其实是希望寻找到一个数x，使得D的值最小，也就是说这个数和所有点的距离的和最小。

现在我们来求使D最小的x值，经过简单变形以后，上式可以写成

经过化简以后的式子后两项是和x没有关系的常数，容易知道，只有当这个项为0时，D的值最小，也就是最佳值是

从这个分析过程看到，当我们希望寻求一个到各点距离最小的值的情况下，最佳的方法是求算术平均值。

有时候，我们还会有别的需求，因此也定义了很多其他的求平均的方法，下面我们来一一学习一下。如果有一组数a₁，a₂，…，an，那么定义

算术平均（Arithmetic mean）：

几何平均（Geometric mean）：

调和平均（Harmonic mean）：

平方平均（Quadratic mean）：

前三种平均数最早由古希腊的毕达哥拉斯学派发现，因此它们合称毕达哥拉斯平均数。在命名上，算术平均、平方平均都很好理解；几何平均来源于两个数相乘，它的几何含义是面积，因此命名为几何平均。那调和平均是什么意思呢？调和这个名字源于泛音及泛音列（泛音列与调和级数英文同为harmonic series）：什么叫泛音，泛音是相对于基音来说的，一条振动的弦会发出一个基音和无数个泛音，基音就是频率最低的纯音，所有其余的纯音称为泛音或陪音。泛音的波长依次是基音波长的1/2、1/3、1/4……也就是说频率是基音的2倍、3倍……就如图35-1所示，黑的曲线表示的是基音，其他颜色的是泛音。数学上把1，1/2，1/3，1/4，…，1/n称为调和级数，因为它的第一项之后的每一项都是相邻两项的调和平均数。

图35-1　基音和泛音的关系

既然有这么多个平均数，在什么时候用什么平均数比较恰当呢，我们来看几个实例。

35.1　算术平均数

算术平均数的名字取得很合适：我们累加数据集中的所有数字，接着除以数据集包含的数字数目。当数字之间存在可加性（Additive）关系的数据集上，算术平均数效果很好。这样的关系经常被称为线性，因为如果我们将所有数字按升序或降序排列，数字倾向于落在一根直线上。一个简单而理想化的例子是公差为3的等差数列，比如：1，4，7，10，13，16，19，…，（1+4+7+10+13+16+19）÷7=10，图35-2中白色点就是算术平均数所处的位置。

图35-2　算术平均数

然而，并不是所有的数据集都适宜用这种关系描述的。有些数据集内部存在相乘或指数关系，例如，公比为3的等比数列：1，3，9，27，81，243，729，…注意这类级数也有个名字叫几何级数（见图35-3）。这几个数的算术平均值为：（1+3+9+27+81+243+729）÷7=156.1。

图35-3　几何级数

我们看到，算术平均数（156）并不特别接近我们的数据集中的大多数数字。实际上，它是中位数（27）的5倍。

将数据绘制在一根数轴上，能够更明显地看到这个值的漂移：

所以，我们做什么呢？引入几何平均数，由于数据集中数字之间的关系是乘性关系，我们通过乘法和取方根（总共有几个数字就开几次方根）来得到几何平均数（见图35-4）：

可以看到，在等比数列上，几何平均数更能代表数据集的中间值。从单根数轴上也可以看到这一点：

图35-4　几何平均数

35.2　几何平均数

实际上，有很多实际场景适合使用几何平均数，因为类似相乘的关系在真实世界中很常见。一个经典的例子是银行存钱时如何计算利息的问题。

假设我们有一笔5年期存款，本金为￥100 000，每年的利率是变动的。

年利率：1%、9%、6%、2%、15%

我们想要找到平均年利率，并据此计算5年后本金和利息的总和。我们尝试“平均”这些利率：

（0.01+0.09+0.06+0.02+0.15）÷5=0.066=6.6%

然后我们将平均利率代入复利计算公式：

100 000×（1.066⁵-1）+100 000=137 653.11

比较一下不使用平均利率，直接计算的结果：

100 000×1.01×1.09×1.06×1.02×1.15=136 883.70

可以看到，简便计算方法的误差约为￥1000。

现在，让我们试试几何平均数：

将几何平均数代入复利计算公式：

100 000×（1.0648⁵-1）+100 000=136 883.70

这个数字正好等于我们逐年计算所得的结果。

从这两种计算方法比较知道，我们用算术平均值计算利率时犯了一个常见的错误：我们将加法操作应用于相乘过程，得到了不精确的结果。这个例子可以看出，如果我们使用合适的平均数，就能得到正确的结果。

几何平均数还适合什么场景呢？

几何平均数的一个很酷的特性是，你可以对尺度完全不同的数字取平均数。

例如，假设我们想比较两间咖啡店来源不同的在线评价。问题在于，来源一的评价使用五星制，而来源二的评价使用百分制。

咖啡店A

来源一：4.5

来源二：68

咖啡店B

来源一：3

来源二：75

如果我们直接根据原始分值计算算术平均数：

咖啡店A=（4.5+68）/2=36.25

咖啡店B=（3+75）/2=39

根据上面的数据，我们得出结论咖啡店B是赢家。

如果我们对数字有一点敏感性，就会知道在应用算术平均数得到精确的结果之前，首先需要标准化（Normalize）数据集中的值至同一尺度。

所以，我们将来源一中的评价乘以20，将其从五星尺度拉伸到来源二的百分制尺度：

#咖啡店A

4.6×20=90

（90+68）/2=79

#咖啡店B

3×20=60

（60+75）/2=67.5

我们发现，其实咖啡店A才是赢家。

然而，几何平均数允许我们在不考虑尺度问题的前提下得到一样的结论：

咖啡店A=（4.5×68）的平方根=17.5

咖啡店B=（3×75）的平方根=15

算术平均数被尺度较大的数字支配了，以至于得出了错误的结果。这是因为算术平均数期望数字间的加法关系，而没有考虑尺度和比例问题。所以需要在应用算术平均数之前将数字转换为同一尺度。

几何平均数很容易就能处理比例问题，因为它本质上是乘法关系。这是一个极为有用的性质，但注意我们损失了什么：我们不再具有可解释的尺度了（可以理解成基本单位和原来不一样了的意思）。在这样的情况下，几何平均数其实是无单位的（unitless）。例如，以上的几何平均数既不意味着百分制中的17.5分，也不意味着五星制中的15星。它们不过是无单位的数字，相互之间比例一致（技术上说，它们的尺度是原尺度5和100的几何平均数，也就是22.361）。不过，如果我们只需比较两间咖啡店评价的高低，那么这不会成为一个问题。

几何平均数回顾：

几何平均数对值相乘，而不是相加，接着取n次方根，而不是除以n。

它基本上是在说：如果我们的数据集中的数字都是一样的，那么这个数字应该是什么，才能得到和实际数据集一样的乘积？

这使它非常适合描述相乘关系，例如比率，即使这些比率的尺度不同。（因此，它经常用来计算财经指数和其他指数。）

缺点：应用几何平均数时，可能会丢失有意义的尺度和单位。另外，它对离散值的不敏感性可能会遮蔽可能具有较大影响的大数值。

和生活中的大多数事情一样，极少有牢不可破的规则说必须使用几何平均数（复利等少数情形除外）。有一些启发式的规则和经验规则，但无疑需要判断力和科学的怀疑，才能应用合理的经验。

在最后的总结中我们将继续讨论这些，不过现在让我们引入最后一种毕达哥拉斯平均数——调和平均数。

35.3　调和平均数

算术平均数需要加法，几何平均数则利用乘法，调和平均数使用倒数。

源自维基百科的一个简单例子：1、4、4的调和平均数是2：

注意，由于0没有倒数，因此调和平均数和几何平均数一样，无法处理包含0的数据集。

好，我们已经明白数学部分如何工作了。不过调和平均数适用于哪些场景呢？

为了回答上面的问题，我们需要回答：倒数适用于哪些场景？我们下面举一个关于平均速度的实际例子。

现实世界中，使用调和平均数的经典例子是以不同的速度通过物理空间。

考虑一次去便利店并返回的行程。

去程速度为30km/h，返程时交通有一些拥堵，所以速度为10km/h，去程和返程走的是同一路线，也就是说距离一样（5km），整个行程的平均速度是多少？

同样，我们可以不假思索地直接应用30和10的算术平均数，然后自豪地宣布结果是20km/h。

但是再想一想：由于你在一个方向上的速度较高，因此你更快地完成了去程的5km，在那个速度上花了整个行程中更少的时间，所以整个行程期间你的平均速度不会是30km/h和10km/h的中点，它应该更接近10km/h，因为你更多的时间是以10km/h的速度行驶。

为了正确地应用算术平均数，我们需要判定以每种速率行驶所花的时间，然后以适当的权重加权算术平均数的计算：

去程：5/（30/60）=10min

返程：5/（10/60）=30min

总行程：10+30=40min

加权算术平均数：（30×10/40）+（10×30/40）=15km/h

所以，我们看到，真正的平均速度是15km/h，比使用未加权的算术平均数计算所得低了5km/h（或者25%）。

你大概猜到了我们下面要做什么……让我们试着使用调和平均数：

2/（1/30+1/10）=15

真正的行程平均速度，自动根据在每个方向上使用的时间进行调整，是15km/h！

有一些地方需要注意：

可以直接应用调和平均数的前提是不同速度行驶的总距离是相等的。如果距离不同，我们需要使用加权调和平均数，或加权算术平均数。

当距离不等时，算术平均数仍然以不同速度行驶的时间作为加权，而调和平均数则以不同速度行驶的距离作为加权（因为通过取倒数，已经隐式地考虑了不同速度的时间比例）。

毕达哥拉斯平均数大部分的复杂性和麻烦源于比率的本质以及我们对比率的哪方面更感兴趣。例如，算术平均数总是用分母的单位表示。在行程问题中，比率是每小时的英里数，因此，算术平均数给出的结果是以分母（某种意义上隐藏的）单位小时表示：（30m/1h+10m/1h）÷2=20m/1h=20m/h。如果我们在每个方向上所花的时间是一样的，那么这个结果会是精确的。然而，我们知道，在每个方向上所花的时间并不一样。相反，调和平均数通过取倒数翻转这些比率，将我们实际感兴趣的数字放入分母，接着取算术平均数，并再次翻转，给出我们要求的平均速度。

几何平均数适用于复利问题的原因是，利率的周期是相等的：每种利率一年。如果周期是可变的，也就是说每种利率的持续时间不同，那么我们同样需要使用某种权重。

几何平均数可以处理相乘关系，例如复利问题和不同评分尺度上的比率，而调和平均数则通过神奇的倒数容纳了另一层次的乘／除关系，例如可变周期或长度。

类似复利问题和几何平均数，这是一个准确、客观正确的调和平均数的应用案例。不过，事情并不总是如此清晰。有其他准确的、可以在数学上论证的调和平均数的应用，包括物理、财经、水文学，甚至（源自传统）棒球统计。和数据科学关系更密切的：调和平均数经常用在评估机器学习模型的准确率和召回中。但是，在更多的情况下，调和平均数的应用需要判断力，需要你对数据和手头问题的灵活理解。

总结：

（1）三种毕达哥拉斯平均数密切相关。

例如，我们已经看到：

不同尺度评分的几何平均数有时保留了这些值标准化至同一尺度后的算术平均数的次序。

调和平均数等价于行程速度的加权算术平均数（权重为相对行程时间）

（2）毕达哥拉斯平均数遵循严格的次序。

根据相应的公式，调和平均数总是小于几何平均数，几何平均数总是小于算术平均数。关于这一结论我们在前面章节已经介绍，读者可以翻回到第27章。

这三种平均数是彼此接近还是相互远离，取决于数据的分布。以上规则唯一的例外是，在数据集中所有数字相等的极端情形下，三种平均数同样相等。也就是说，以下不等关系成立：

调和平均数≤几何平均数≤算术平均数

从本节开头的毕达哥拉斯平均数的几何描述中也能看到这一点。

认识到这一次序关系有助于理解何时应用哪种平均数，以及不同平均数对结果的影响。

让我们回顾之前的相加和相乘数据集，这次我们将画出所有三种平均数，相加数据集见图35-5，相乘数据集见图35-6。

图35-5　三种平均数（相加数据集）

很明显，几何平均数和调和平均数看起来要比这一线性、相加数据集的中间低不少。这是因为这两种平均数对较小的数字而不是较大的数字更敏感（让它们相对而言对较大的离散值不敏感）。

这里，几何平均数准确地位于数据集的中点，而调和平均数则向低端偏移，算术平均数则受较大的离散值的影响，向高端偏移。

图35-6　三种平均数（相乘数据集）

本章前面动图的生成都很简单，直接用matplotlib的plot函数就可以，读者可以参考以下示例：

上面例子中用到了一些对线的样式的控制，读者可以翻看第10章的参数解释。其中mec（Marker Edge Color）控制Marker的边缘颜色，mfc（Marker Face Color）控制Marker的填充色。

36　等周定理及图形化

【本章摘要】

数学内容：等周问题，多边形面积公式，海伦公式。

编程内容：多个图形协同变化的GIF动图实现方法。

大家都吹过肥皂泡吧，可曾想过吹出来的为什么总是一个圆球而不是一个圆柱或是其他形状？如果你给花浇水，为什么叶子上残留的水总是一颗颗球形水珠？对于这些自然现象，你也许会说，“因为在表面张力的作用下，液体总是会力求使其表面积达到最小”。这个物理解释的背后，其实蕴涵了这么一个数学原理，即：在体积不变的情况下，球体具有最小的表面积。接下来我们还可以自己动手来做一个有趣的实验。

图36-1　表面张力实验

[实验]如图36-1所示，将一条固定长度20cm的细线两头连接起来，围成一个任意形状的封闭曲线，再用细线把这个封闭小圈拴在一个半径大概为15cm的大环上，确保整个环的周长比整个小圈要大不少就可以，把整个环泡在肥皂水里，拿起来的时候，确保细线不是粘在一起而是张开的即可。这个时候，如果拿一个牙签把细线内的肥皂膜刺破，猜猜会发生什么？

答案：你会发现，这个线圈变成了一个圆！这背后的道理是表面张力试图从各个方向拉扯这个封闭曲线，扩大它的面积，最后拉成了一个圆。它实际上又告诉我们另一个数学原理：在周长相同的所有封闭平面曲线中，以圆的面积为最大。

上面两个数学原理有一个相同的特点，即在对周界加上一些限制后，某些平面图形具有最大的面积，这一类型的数学问题统称为等周问题。

我们先来看几个简单的等周问题。

问题1：在周长一定的一切三角形中，什么三角形面积最大？

问题2：在周长一定的一切四边形中，什么四边形面积最大？

其实这两个问题是同一个问题，答案是正多边形面积最大，当多边形的边数n趋近无穷大时，就是圆，圆在周长一定的封闭平面曲线中面积最大。这个命题的证明有点难，我们可以用前面学过的一个不等式来证明问题1，然后类推出去。我们知道：

这个不等式有另一个简写版就叫G≤A，A代表算术平均，而G叫几何平均。当所有的项相等时等号成立。当n=3时，这个公式化简成

或者等价形式

这个公式要求α、β、γ都是正数。

设a=γ+β，b=γ+α，c=α+β，由于a，b，c三个正数中，任两个数之和都大于第三个数，因此以a，b，c三数为边一定可以构造出一个三角形。因此存在边长分别为a、b、c的三角形，其面积用海伦公式可以写成

其中，为半周长。我们发现，p-a=α，所以上式又可以写成

回到我们要证明的命题，周长一定的三角形中正三角形面积最大，就是说需要证明p=α+γ+β为常数的情况下，正三角形面积最大。

由前面不等式知道

所以，

这个不等式成立的条件是α=β=γ，由此推出三边相等时，面积最大。

由此我们正式引出等周定理（Isoperimetric Inequality）：

若P为封闭曲线的周界长，A为曲线所包围的区域面积，则

4πA≤P²

关于等周定理，维吉尔的作品《埃涅阿斯纪》（Virgil's Aeneid）就有记载。《埃涅阿斯纪》全书12卷，成书于大约公元前30—前19年，讲述特洛伊王子埃涅阿斯在特洛伊被古希腊军队灭亡后，如何克服重重困难重建古罗马的故事。书中记录了埃涅阿斯的情人迦太基女王狄朵被她的兄长追杀，被迫离开故土，逃到了北部非洲，为购买土地与当地人经历了一番讨价还价，最终羞辱性地得到一个承诺，她可以买一块牛皮可以包住的土地，于是聪慧的狄朵将牛皮切成尽可能多的细条，将细条相连成线靠海围成一个半圆，从而围住了大片土地（见图36-2），这个地方后来就叫“公牛皮之城”（Bull's Hide）。在这里我们看到了等周问题的影子，即在给定的周长情况下围住尽可能多的面积。

图36-2　狄朵问题示意图

我们下面来编一个程序，来计算一下，看看周长一定的情况下，随着边数的增多，正多边形面积变化的情况（见图36-3）。

图36-3　随边数的增多，正多边形面积变化的情况

等周定理动图

图36-3中上面表示的是正多边形的形状，下图绿色曲线是对应的多边形的面积。从这个图中看出，随着边数的增多，多边形面积越来越接近圆的面积。到了正12边形以后，圆面积和多边形面积就非常接近了。

参考文献

［1］Kevin Poulsen. How a Math Genius Hacked OkCupid to Find True Love. [EB/OL](2020-07-22).

https://www.wired.com/2014/01/how-to-hack-okcupid/.

［2］杨克昌．趣味数学及编程拓展[M]．北京：清华大学出版社，2019．

［3］Kevin Hartnett. Mathematician Proves Huge Result on ‘Dangerous’ Problem. [EB/OL](2020-07-22).

https://www.quantamagazine.org/mathematician-terence-tao-and-the-collatz-conjecture-20191211/.

［4］搜狐．考拉兹猜想：数学还不知如何应对的问题．[EB/OL]（2020-07-22）．

https://www.sohu.com/a/157051762_224832.

［5］果果．阿基米德的数学成就之圆的测定．[EB/OL]（2020-07-22）．

https://wenku.baidu.com/view/21c7aaf3f61fb7360b4c6502.html.

［6］Bill Casselman. The method of exhaustion. [EB/OL], (2020-07-22).

http://www.math.ubc.ca/~cass/courses/m446-03/exhaustion.pdf.

［7］知乎．古人是如何寻找到π的？[EB/OL]（2020-07-22）.

https://zhuanlan.zhihu.com/p/48307582.

［8］Stackoverflow.斐波那契数列和向日葵的果实排列．[EB/OL]（2020-07-22）.

https://stackoverflow.com/questions/30353575/fibonacci-sunflower-tkinter/30355276.

［9］Deborah R. Fowler. Phyllotactic Pattern. [EB/OL](2020-07-22).

http://deborahrfowler.com/MathForVSFX/Phyllotaxis.html.

［10］Strauss S, Lempe J, Prusinkiewicz P, et al. Phyllotaxis: is the golden angle optimalfor light capture?. New Phytologist, 225:499-510, 2020.

http://algorithmicbotany.org/papers/Strauss2019pit.html.

［11］莫水干流．旋转矩阵．[EB/OL]（2020-07-22）.

https://www.cnblogs.com/zhoug2020/p/7842808.html.

［12］Syed N R.排序算法的图形化方法．[EB/OL]（2020-07-22）.

https://nrsyed.com/2018/09/27/visualizing-sorting-algorithms-and-time-complexity-with-matplotlib/.

［13］杨照琨．摘麦穗问题．[EB/OL]（2020-07-22）.

http://episte.math.ntu.edu.tw/articles/sm/sm_03_05_1/page2.html.

［14］知乎．人们专门弄了一个自然对数函数的底数e，是为什么？.[EB/OL]（2020-07-22）

https://www.zhihu.com/question/24264370.

［15］McNichol D.平均而言你用的是错误的平均数[EB/OL]（2020-07-22）.

https://towardsdatascience.com/on-average-youre-using-the-wrong-average-geometric-harmonic-means-in-data-analysis-2a703e21ea0.

［16］知乎．如何理解与应用调和平均数？[EB/OL]（2020-07-22）.

https://www.zhihu.com/question/23096098.

［17］华罗庚，史济怀．数学小丛书（第一卷）[M]．北京：科学出版社，2018.

［18］张景中．从数学教育到教育数学[M]．北京：中国少年儿童出版社，2005.

［19］Stern D P.月亮离我们有多远．[EB/OL]（2020-07-22）.

https://wwwpof.gsfc.nasa.gov/stargaze/Shipparc.htm.