书名:线性代数与Python解法
ISBN:978-7-115-60669-3
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
编 著 徐子珊
主 审 刘新旺
责任编辑 张 涛
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
本书共5章:第1章介绍代数系统的基本概念, 内容包括集合与映射、群、环、域及线性代数系统等; 第2章介绍矩阵代数, 内容包括矩阵定义、矩阵的各种运算, 如线性运算、乘法、转置、方阵的行列式等, 并由此讨论可逆阵的概念及性质; 第3章介绍线性方程组的消元法, 为后面讲解向量空间的知识奠定基础; 第4章基于矩阵、线性方程组等讨论应用广泛的向量空间, 内容包括向量及其线性运算、向量组的线性相关性、线性空间的线性变换等; 在以上几章的基础上, 第5章定义向量的内积运算, 在向量空间中引入“度量”, 即向量的长度(范数), 从而将二维、三维的几何空间扩展到一般的n维欧几里得空间.
本书选择Python的科学计算的软件包NumPy作为计算工具, 针对书中讨论的线性代数的计算问题给出详尽的Python解法. 本书中的每一段程序都给出了详尽的注释及说明, 适合各层次读者阅读.
本书以“代数系统”为引领,以“演绎”的方式探讨线性代数的理论与方法.第1章介 绍“代数系统”概念——定义了若干运算的集合,以及“经典代数系统”,即群、环、域.以 此为起点引入“线性代数系统”,即在一个加法交换群上,添加群中元素与数域中数的乘法 运算而得的代数系统——加法运算和数乘法运算统称为线性运算.线性代数系统是很多自 然系统与人工系统的数学模型.最经典的线性代数系统之一是第2章详尽讨论的“矩阵代 数”,其核心是同形矩阵(具有相同行数、列数的矩阵)集合上的加法和数乘法构成的一个 线性代数系统.然而,矩阵集合仅作为线性代数系统,在实际应用上是不够的.在引入矩阵 的“乘法”运算后,矩阵代数就成为描述各种问题的重要数学模型.矩阵代数最重要的应用 领域之一是本书第3章介绍的线性方程组.基于矩阵的各种运算及性质,第3章不仅给出 线性方程组的解法,而且给出了确定方程组的有解条件、解集的结构等重要的结论.这些结 论为更深入地探讨第4章中的向量代数系统提供了强有力的计算方法.向量空间不仅是二 维平面和三维立体的理论拓展,还可用以描述现实问题.例如,对第2章引入的矩阵,我 们可更细致地把矩阵拆解成行向量或列向量,矩阵的线性运算平移到了向量上,矩阵的乘 法拆解成了行向量与列向量的“内积”;于是,线性方程组的矩阵形式被拆解成了向量形式, 进而成了研究向量间线性关系的“利器”.由于引入了同形向量的内积运算,使得高维向量 空间与我们看到的二维平面和三维立体一样有了 “几何形象”:向量有“长度”——范数、 向量间有夹角,这是本书第5章讨论欧几里得空间的有趣内容.总之,本书以发展的观点 讨论线性代数:向量(从同构的视角看,矩阵亦可被视为向量)由最核心的交换群增加数乘 法后构成线性代数系统、加入内积运算后构成欧几里得空间……这未尝不是我们构建人造 系统的一种思想方法:把问题涉及的对象视为集合,从最基本的处理(运算)方法开始,逐 步添加所需的处理(运算)方法,使系统日臻完善.
华罗庚先生在《高等数学引论》的序言中写道:“我讲书喜欢埋些伏笔,把有些重要的 概念、重要的方法尽可能早地在具体问题中提出,并且不止一次地提出.”先生的意思是学 习者能在书中不同地方逐步体会到这些重要概念、方法的精妙之处.笔者在本书的写作过 程中也试着仿照先生的做法:将重要的结论拆分成若干个引理、定理和推论;一些重要但 较简单的概念在例题和练习题中提出,让读者在稍后阅读到这些内容时发现这些概念的关 键作用.在快节奏的现代社会,读者的时间非常宝贵,为此,笔者将一些定理(包括引理、 推论)的比较复杂的理论证明以“本章附录”的形式,放在每章(除第1章夕卜)的末尾,待 读者空闲时仔细研读.这样的内容安排既可以让读者流畅地阅读,同时又可以帮助读者深 入理解、掌握理论的来龙去脉(对于数学书,笔者极力主张弄懂知识的“来龙去脉”的学习 方法).例题和练习题是理工科图书的作者与读者思想交互的重要“桥梁”,本书共提供了 145道例题、107道练习题.每道练习题都由其之前的例题作为引导,读者可参考相关例题, 顺利完成练习题的解答.此外,每道计算型的练习题均有参考答案,可供读者快速检验自己 的解题结果.
近年来,Python及其数学包“异军突起”,除了因为其开放代码资源,还因为其代码 可直接被嵌入智能系统.本书用Python的科学计算的软件包NumPy来求解书中的所有 问题;选择Jupyter Notebook作为程序编写和运行平台,Jupyter Notebook的使用界面与 MATLAB十分接近,非常适合用来做科学计算.本书的所有程序都经过精心调试,并有详尽 的注释及说明.为方便读者学习,程序以chapterxx.ipynb(xx表示章的序号)的形式命名. 读者可先启动Jupyter Notebook,然后打开对应文件,调试、运行各个程序.所有的.ipynb 文件和自编的通用函数文件utility.py都保存在笔者的Gitee账号https://gitee.com/xu- zishan下的Algebra-with-Python文件夹内,读者可自行下载并使用这些文件.笔者开通了 博客(博客网址是https://blog.csdn.net/u012958850),并将持续维护、更新博文,还会在 博客中添加新的例题以及本书的勘误信息,欢迎读者通过博客与笔者沟通、交流.
本书在描述线性代数的基本理论与方法时,尽量采用目前国内外大多数教材上的通用 表述法,以便读者阅读.为了与程序代码中的常数“0”进行区分,本书将零向量及仅含一 列零或一行零的矩阵用粗斜体小写字母“o”表示,将一般的m行n列的零矩阵用粗斜体 大写字母“0”表示,特此说明.
感谢国防科技大学的刘新旺教授在百忙之中担任本书的主审,为全书的审校工作付出 了艰辛的劳动.在本书的编写过程中,刘新旺教授课题组的博士生团队参与了讨论并对书 稿提出了大量修改意见,其中,梁科、欧琦媛博士负责第1章,刘吉元、杨希洪博士负责 第2章,涂文轩、刘悦博士负责第3章,王思为、文艺博士负责第4章,梁伟轩、文艺博 士负责第5章,梁伟轩博士负责各章工作小组的联络、沟通.在此,笔者向刘新旺教授及其 团队表示衷心的感谢!
本书编辑联系邮箱为:zhangtao@ptpress.com.cn.
徐子珊
 1.1 代数
 1.1 代数将所研究问题涉及的诸对象视为一个整体,称为集合,常用大写字母  表示. 常见的由数组成的集合有自然数集、整数集、有理数集、实数集和复数集,分别记为
 表示. 常见的由数组成的集合有自然数集、整数集、有理数集、实数集和复数集,分别记为 、
、 、
、 、
、 和
 和  . 组成集合的每一个对象,称为该集合的一个元素,常用小写字母
 . 组成集合的每一个对象,称为该集合的一个元素,常用小写字母  表示. 若
 表示. 若  是集合
 是集合  中的元素,则记为
 中的元素,则记为  ,否则记为
 ,否则记为  . 例如,
 . 例如,  但
 但  ,
 ,  但
 但  . 若集合
 . 若集合  中的元素均为集合
 中的元素均为集合  中的元素,即
 中的元素,即  ,必有
 ,必有  ,称
 ,称  是
 是  的子集,记为
 的子集,记为  . 例如,
 . 例如,  .
 .
无任何元素的集合称为空集,记为  . 如果非空集合
 . 如果非空集合  中的元素可一一罗列出来, 则可用花括号把这些罗列出来的元素括起来. 例如,不超过 5 的正整数组成的集合
 中的元素可一一罗列出来, 则可用花括号把这些罗列出来的元素括起来. 例如,不超过 5 的正整数组成的集合  
  . 也可以用描述的方式表示一个具体的集合,例如,不超过 5 的正整数组成的集合可表示为
 . 也可以用描述的方式表示一个具体的集合,例如,不超过 5 的正整数组成的集合可表示为  .
 .
常将集合直观地表示为平面上的封闭区域, 集合中的元素为区域内的点, 子集的图示如图 1.1 所示.
图1.1 子集的图示
实践中所面对的问题往往涉及两个甚至更多的集合. 集合的元素之间, 通常具有某种关系.
定义1.1 设  为两个非空集合,若按确定的法则
 为两个非空集合,若按确定的法则  与之对应[1], 记为
 与之对应[1], 记为  ,则称
 ,则称  为
 为  到
 到  的一个映射 (或变换),记为
 的一个映射 (或变换),记为  . 若
 . 若  ,即
 ,即  ,则称
 ,则称  为
 为  上的映射.
 上的映射.
[1] 数理逻辑中,存在量词 表示 “至少存在一个……”. 本书用
表示 “至少存在一个……”. 本书用 表示 “恰存在一个……”.
表示 “恰存在一个……”.
若  ,称
 ,称  是
 是  的像,
 的像,  是
 是  的原像.
 的原像.  中元素
 中元素  的像,常记为
 的像,常记为  .
 .
例1.1 有限集合 (元素个数有限) 间的映射,可以列表表示. 设  , 定义
 , 定义 到
 到  的对应法则
 的对应法则  :
 :
 就是
 就是  中元素对应相反数的映射.
 中元素对应相反数的映射.
练习1.1 设  ,即比特集,其中的元素 0 和 1 是二进制数. 用列表方式表示比特集
 ,即比特集,其中的元素 0 和 1 是二进制数. 用列表方式表示比特集  上的映射
 上的映射  .
 .
(参考答案:  )
 )
非空集合  的元素之间的对应法则
 的元素之间的对应法则  要成为
 要成为  到
 到  的映射,需满足如下两个条件.
 的映射,需满足如下两个条件.
(1)  中每个元素
 中每个元素  均有在
 均有在  中的像
 中的像  .
 .
(2)  中任一元素
 中任一元素  在
 在  中只有一个像
 中只有一个像  .
 .
图 1.2(a) 表示  到
 到  的映射; 图 1.2(b) 中由于
 的映射; 图 1.2(b) 中由于  中存在元素在
 中存在元素在  中无像,故对应法则不是
 中无像,故对应法则不是  到
 到  的映射; 图 1.2(c) 中由于
 的映射; 图 1.2(c) 中由于  中存在元素对应
 中存在元素对应  中的两个元素,故对应法则也不是
 中的两个元素,故对应法则也不是  到
 到  的映射.
 的映射.
图1.2 集合元素的对应法则
例1.2 设  ,考虑函数
 ,考虑函数  
  ,不难理解它们均为
 ,不难理解它们均为  上的映射. 然而,函数
 上的映射. 然而,函数  [见图1.3(a)]是
 [见图1.3(a)]是 上“1-1”的映射,即一个原像
上“1-1”的映射,即一个原像  仅对应一个像
 仅对应一个像  . 反之,任一
 . 反之,任一  也仅有一个原像
 也仅有一个原像  . 这样的映射是可逆的,因为据此对应法则,我们可以得到逆映射 (即反函数)
 . 这样的映射是可逆的,因为据此对应法则,我们可以得到逆映射 (即反函数)  . 但是函数
 . 但是函数  [见图1.3(b)]却不是可逆的. 这是因为, 首先对于
 [见图1.3(b)]却不是可逆的. 这是因为, 首先对于  且
 且  ,在
 ,在  中没有与之对应的原像. 其次,对于
 中没有与之对应的原像. 其次,对于  且
 且  中有两个原像
 中有两个原像  和
 和  与之对应. 换句话说,根据映射
 与之对应. 换句话说,根据映射  的对应法则,不能构造出其逆映射.
 的对应法则,不能构造出其逆映射.
图1.3 可逆与不可逆函数
以上讨论的集合  到
 到  的映射,原像均为取自集合
 的映射,原像均为取自集合  的一个元素,这样的映射称为一元映射. 实践中,集合
 的一个元素,这样的映射称为一元映射. 实践中,集合  的结构也许要稍稍复杂一些.
 的结构也许要稍稍复杂一些.
定义1.2 设集合  和
 和  非空,有序二元组集合
 非空,有序二元组集合  称为
 称为  与
 与  的笛卡儿积,记为
 的笛卡儿积,记为  .
 .
在代数学中,非空集合  上的映射称为一元运算,
 上的映射称为一元运算,  到
 到  的映射称为
 的映射称为  上的二元运算. 常用运算符来表示运算,如例 1.1 中,集合
 上的二元运算. 常用运算符来表示运算,如例 1.1 中,集合  上的取相反数的映射
 上的取相反数的映射  即负数运算,这是一个一元运算,其运算符常表示为 “-”.
 即负数运算,这是一个一元运算,其运算符常表示为 “-”.  ,对应
 ,对应  . 练习1.1 中的
 . 练习1.1 中的  上的一元映射即对比特位 (二进制位) 的取反运算,这也是一个一元运算,其运算符常表示为 “
 上的一元映射即对比特位 (二进制位) 的取反运算,这也是一个一元运算,其运算符常表示为 “  ”.
 ”.  ,对应
 ,对应  . 利用练习1.1 的计算结果得比特集
 . 利用练习1.1 的计算结果得比特集  上的取反运算表为
 上的取反运算表为
例1.3 考虑比特集  上的 “或” 运算 “V”:
 上的 “或” 运算 “V”:  当且仅当
 当且仅当  时成立. 根据
 时成立. 根据  运算的这一定义,可得其运算表:
 运算的这一定义,可得其运算表:
练习1.2 比特集  上的 “与” 运算 “
 上的 “与” 运算 “  ”:
 ”:  当且仅当
 当且仅当  时成立. 试给出
 时成立. 试给出  的运算表.
 的运算表.
(参考答案: )
)
例1.4 自然数集  上的加法运算就是
 上的加法运算就是  到
 到  的二元映射. 但是,减法运算不是
 的二元映射. 但是,减法运算不是  上的二元运算,因为对于
 上的二元运算,因为对于  ,且
 ,且  . 换句话说,对于
 . 换句话说,对于  ,若
 ,若  则
 则  ,即
 ,即  在
 在  中没有像.
 中没有像.
集合  上的运算结果必须属于集合
 上的运算结果必须属于集合  的要求,称为运算对集合
 的要求,称为运算对集合  的封闭性. 不难验证,数的加法和乘法运算对
 的封闭性. 不难验证,数的加法和乘法运算对  、
、  、
、  、
、  和
和  都是封闭的. 例 1.4 说明数的减法运算对
 都是封闭的. 例 1.4 说明数的减法运算对  不具有封闭性,但对
 不具有封闭性,但对  、
、  、
、  和
 和  都是封闭的. 数的除法运算对
 都是封闭的. 数的除法运算对  和
 和  不具有封闭性,但对
 不具有封闭性,但对  和
 和  都是封闭的.
 都是封闭的.
将  到
 到  的映射
 的映射  ,视为二元运算 “
 ,视为二元运算 “  ”:
 ”:  . 这时,需注意一个细节: 一般而言,
 . 这时,需注意一个细节: 一般而言,  是一个序偶,由于
 是一个序偶,由于  与
 与  未必相同,故
 未必相同,故  ,但未必有
 ,但未必有  . 即使
 . 即使  ,但
 ,但  未必与
 未必与  相同. 例如,
 相同. 例如,  .
 .  不全为零,则
 不全为零,则  . 对于一个二元运算 “o”:
 . 对于一个二元运算 “o”:  及
 及  ,
 ,  ,则称该运算具有交换律. 例如,数的加法运算 “+”,在数集
 ,则称该运算具有交换律. 例如,数的加法运算 “+”,在数集  、
、  、
、  、
、  和
 和  上都具有交换律.
 上都具有交换律.
代数学研究的主要对象是代数系统,简称代数,即非空集合  及定义在
 及定义在  上的若干运算
 上的若干运算  . 其中的运算
 . 其中的运算  可以是一元运算,也可以是二元运算. 通常将代数记为
 可以是一元运算,也可以是二元运算. 通常将代数记为  ,在不会产生混淆的情况下可将代数系统简记为
 ,在不会产生混淆的情况下可将代数系统简记为  . 代数学对各种代数系统研究定义在
 . 代数学对各种代数系统研究定义在  上各种运算的性质以及由这些运算及其性质所决定的集合
 上各种运算的性质以及由这些运算及其性质所决定的集合  的逻辑结构.
 的逻辑结构.
例1.5 设  表示字符集,
 表示字符集,  表示由
 表示由  中字符组成的有限长度的字符串全体 (含空字符串
 中字符组成的有限长度的字符串全体 (含空字符串  构成的集合.
 构成的集合.  ,定义
 ,定义 为
 为  与
 与  连接而成的字符串,则
 连接而成的字符串,则  构成一个代数系统, 该代数系统是信息技术中最重要的处理对象之一.
 构成一个代数系统, 该代数系统是信息技术中最重要的处理对象之一.
例1.6 考虑比特集  [2],练习1.1、例 1.3 及练习1.2,在
[2],练习1.1、例 1.3 及练习1.2,在  上定义的 3 个运 算分别为
 上定义的 3 个运 算分别为
[2]  中的元素 0 和 1 可视为比特位,也可视为逻辑假 (False) 和逻辑真 (True).
中的元素 0 和 1 可视为比特位,也可视为逻辑假 (False) 和逻辑真 (True).
代数系统  即为著名的布尔代数. 其中
 即为著名的布尔代数. 其中  和
 和  是二元运算,分别称为或和与运算. コ为一元运算, 称为非运算. 布尔代数的运算有如下性质.
 是二元运算,分别称为或和与运算. コ为一元运算, 称为非运算. 布尔代数的运算有如下性质.
(1) 或运算交换律:  .
 .
(2) 或运算结合律:  .
 .
(3) 或运算 0 -元律:  .
 .
(4) 与运算交换律:  .
 .
(5) 与运算结合律:  .
 .
(6) 与运算 1-元律:  .
 .
(7) 与运算对或运算的分配律:  .
 .
(8) 或运算对与运算的分配律:  .
 .
(9) 反演律:  .
 .
布尔代数  是数理逻辑乃至电子计算机技术的基础数学模型. 其中各运算的所有性质均可用 “真值表” 加以验证. 以证明性质 (9) 中反演律之一的
 是数理逻辑乃至电子计算机技术的基础数学模型. 其中各运算的所有性质均可用 “真值表” 加以验证. 以证明性质 (9) 中反演律之一的  
  为例,说明如下:
 为例,说明如下:
注意,真值表中的前两列罗列出了  的所有可能的取值,而最后两列分别表示
 的所有可能的取值,而最后两列分别表示  和
 和  在
 在  的所有可能的取值下均相等,故
 的所有可能的取值下均相等,故  
  .
 .
练习1.3 运用真值表,验证布尔代数  中的反演律
 中的反演律  . (提示: 参考对
 . (提示: 参考对  的证明)
 的证明)
我们知道,代数系统中定义在集合  上的各个运算必须是 “封闭” 的,即运算结果必须仍属于集合
 上的各个运算必须是 “封闭” 的,即运算结果必须仍属于集合  .
 .
例1.7 设  ,整数加法并非定义在
 ,整数加法并非定义在  上的运算,因为虽然
 上的运算,因为虽然  ,但
 ,但  . 同样地,
 . 同样地,  也不属于
 也不属于  . 换句话说,
 . 换句话说,  中元素对整数加法运算不是封闭的. 所以
 中元素对整数加法运算不是封闭的. 所以  并不能够成为一个代数系统.
 并不能够成为一个代数系统.
若将  上的 “加法” 定义为:
 上的 “加法” 定义为:  ,
 ,
即  的运算结果是
 的运算结果是  除以 4 的余数——称为模 4 的加法. 此时
 除以 4 的余数——称为模 4 的加法. 此时  ,
 ,  ,因此 “+” 对
 ,因此 “+” 对  是封闭的,于是,
 是封闭的,于是,  构成一个模 4 的剩余类加法系统 (详见例 1.10).
 构成一个模 4 的剩余类加法系统 (详见例 1.10).
 1.2 经典代数系统
 1.2 经典代数系统定义1.3 若代数  的二元运算 “
 的二元运算 “  ” 具有如下性质,则称
 ” 具有如下性质,则称  为一个群.
 为一个群.
(1) 结合律:  .
 .
(2) 零元律:  称为零元,
 称为零元, 
(3) 负元律:  称为
 称为  的负元,使得
 的负元,使得  . 元素
 . 元素  的负元常记为
 的负元常记为  .
 .
若运算  还满足交换律,则
 还满足交换律,则  称为交换群.
 称为交换群.
例1.8 在比特集  上定义运算
 上定义运算  :
 :
称为  上的异或运算. 由上面的运算表可以看到异或运算的一个有趣之处在于:
 上的异或运算. 由上面的运算表可以看到异或运算的一个有趣之处在于:  ,
 ,  (见运算表的第 1 行或第 1 列),
 (见运算表的第 1 行或第 1 列),  (见运算表的第 2 行或第 2 列). 下面说明
 (见运算表的第 2 行或第 2 列). 下面说明  构成一个交换群.
 构成一个交换群.
(1) 由运算表关于从左上角到右下角的对角线的对称性得知,  运算具有交换律;
 运算具有交换律;
(2) 构造真值表
真值表中最后两列的值完全相同,这就验证了  满足结合律
 满足结合律  ;
 ;
(3) 由  的运算表得知 0 是零元;
 的运算表得知 0 是零元;
(4) 由  的运算表得知 0 和 1 均为自身的负元.
 的运算表得知 0 和 1 均为自身的负元.
交换群  在计算机的位运算中扮演着重要角色.
 在计算机的位运算中扮演着重要角色.
练习1.4 例 1.5 中的字符串代数  是否构成一个群?
 是否构成一个群?
(参考答案: 否. 提示: 无负元)
例1.9 代数系统  中
 中  为全体整数的集合,+ 表示两个整数的加法运算,构成一个交换群, 称为整数群.
 为全体整数的集合,+ 表示两个整数的加法运算,构成一个交换群, 称为整数群.
常将群  的运算
 的运算  称为 “加法” 运算,根据
 称为 “加法” 运算,根据  的负元律的意义及记号,可得
 的负元律的意义及记号,可得  上的 “减法” 运算
 上的 “减法” 运算  . 因此,在例 1.9 的整数群
 . 因此,在例 1.9 的整数群  中既可以做加法, 也可以做减法.
 中既可以做加法, 也可以做减法.
例1.10 给定正整数  ,考虑集合
 ,考虑集合  . 在
 . 在  上定义运算
 上定义运算
则  构成交换群.
 构成交换群.
事实上,由运算表定义的 + 运算,就是  中的两个元素之和以
 中的两个元素之和以  为模的余数,即
 为模的余数,即  为
 为 
(1) 根据运算表的对称性得知, + 运算满足交换律
(2)  ,即 + 运算满足结合律
 ,即 + 运算满足结合律
(3) 观察运算表的首行和首列可知 0 是 + 运算的零元;
(4)  且
 且  的负元为
 的负元为  .
 .
 称为模
 称为模  的剩余类加群.
 的剩余类加群.
定义1.4 若代数  的二元运算
 的二元运算  和
 和  具有如下性质,则称
 具有如下性质,则称  为一个环.
 为一个环.
(1)  对于运算
 对于运算  构成交换群.
 构成交换群.
(2) 运算  的结合律:
 的结合律:  .
 .
(3) 运算  对
 对  的分配律:
 的分配律:  且
 且  
 
例1.11 整数集  对于数的加法和乘法构成的代数
 对于数的加法和乘法构成的代数  构成一个环,常称为整数环. 有理数集
 构成一个环,常称为整数环. 有理数集  、实数集
 、实数集  以及复数集
 以及复数集  对于数的加法和乘法构成的代数也分别构成一个环.
 对于数的加法和乘法构成的代数也分别构成一个环.
练习1.5  为全体自然数的集合,代数
为全体自然数的集合,代数  是否构成一个环? 其中 + 和分别表示数的加法和乘法.
 是否构成一个环? 其中 + 和分别表示数的加法和乘法.
(参考答案: 否. 提示:  不满足负元律,不能构成一个群)
 不满足负元律,不能构成一个群)
常将环  的运算
 的运算  称为 “乘法”. 虽然在环
 称为 “乘法”. 虽然在环  中可以进行 “加法” “减法” (“加法” 的逆运算) 和 “乘法”, 但未必能进行 “除法” —— “乘法” 的逆运算.
 中可以进行 “加法” “减法” (“加法” 的逆运算) 和 “乘法”, 但未必能进行 “除法” —— “乘法” 的逆运算.
定义1.5 若代数  的二元运算
 的二元运算  和
 和  具有如下性质,则称
 具有如下性质,则称  为一个域.
 为一个域.
(1)  对于
 对于  和
 和  构成一个环.
 构成一个环.
(2)  的交换律:
 的交换律:  .
 .
(3)  的幺元律:
 的幺元律:  称为幺元,使得
 称为幺元,使得  .
 .
(4)  的逆元律:
 的逆元律:  且
 且  称为
 称为  的逆元,使得
 的逆元,使得  . 非零元素
 . 非零元素  的逆元常记为
 的逆元常记为  .
 .
例1.12 在例 1.8 的交换群  的基础上添加
 的基础上添加  上的与运算
 上的与运算  (参见例 1.6),即在
 (参见例 1.6),即在  上有两个二元运算:
 上有两个二元运算:
由例 1.8 得知,  构成一个交换群.
 构成一个交换群.
由例 1.6 得知,  上的与运算
 上的与运算  满足交换律和结合律,且 1 是其幺元. 根据运算表可知,
 满足交换律和结合律,且 1 是其幺元. 根据运算表可知,  中唯一的非零元素 1 的逆元是其本身. 最后构造真值表
 中唯一的非零元素 1 的逆元是其本身. 最后构造真值表
根据观察,最后两列数据完全一致,可知与运算  对异或运算
 对异或运算  具有分配律.
 具有分配律.
综上所述,  构成一个域.
 构成一个域.
例1.13 根据域的定义,不难验证代数  和
 和  都构成域,分别称为有理数域、实数域和复数域. 但是,在整数环
 都构成域,分别称为有理数域、实数域和复数域. 但是,在整数环  中,任何非零元素对乘法运算没有逆元, 故不能构成域.
 中,任何非零元素对乘法运算没有逆元, 故不能构成域.
由域  中 “乘法”
 中 “乘法”  的逆元律得知,对
 的逆元律得知,对  中的任一元素
 中的任一元素  及非零元素
 及非零元素  ,可进行“除法” 运算
 ,可进行“除法” 运算  . 也就是说,在域
 . 也就是说,在域  中可以进行 “加” 减” “除” 除” “除? 四则运算. 这完全符合我们对有理数域
 中可以进行 “加” 减” “除” 除” “除? 四则运算. 这完全符合我们对有理数域  、实数域
 、实数域  及复数域
 及复数域  的 认知.
 的 认知.
定义1.6 设  为一个交换群,运算 “+” 称为加法.
 为一个交换群,运算 “+” 称为加法.  为一个数域,若
 为一个数域,若  ,
 ,  ,对应唯一的元素
 ,对应唯一的元素  ,记为
 ,记为  ,即
 ,即
常称 “.” 为数与  中元素的乘法,简称数乘法[3] . 数乘运算满足下列性质.
 中元素的乘法,简称数乘法[3] . 数乘运算满足下列性质.
[3] 准确地说,数乘运算 “.” 是 到
到 的一个二元映射.
的一个二元映射.
(1) 交换律:  .
 .
(2) 结合律:  .
 .
(3) 对数的加法 + 的分配律:  .
 .
(4) 对元素的加法 + 的分配律:  . [4]
. [4]
[4] 此处因 为数域,故自身具有加法 (运算符仍用 “+”) 和乘法 (连写,不用运算符) 运算,注意在上下文中与
为数域,故自身具有加法 (运算符仍用 “+”) 和乘法 (连写,不用运算符) 运算,注意在上下文中与 中元素的加法和数乘运算加以区别.
中元素的加法和数乘运算加以区别.
 中元素的加法运算 “+” 连同数乘运算 “.” 统称为线性运算. 定义了线性运算的集合
 中元素的加法运算 “+” 连同数乘运算 “.” 统称为线性运算. 定义了线性运算的集合  称为数域
 称为数域  上的一个线性代数或线性空间,记为
 上的一个线性代数或线性空间,记为  .
 .
例1.14 设  为一数域,
 为一数域,  ,由符号
 ,由符号  和常数
 和常数  构成的表达式
 构成的表达式
称为数域  上
 上  的一元多项式,简称为多项式. 其中,符号
 的一元多项式,简称为多项式. 其中,符号  称为变元,
 称为变元,  称为
 称为  次项,
 次项,  为
 为  次项的系数,
 次项的系数,  . 非零系数的最大下标
 . 非零系数的最大下标  ,称为多项式的次数.
 ,称为多项式的次数.  时,0 次多项式为一常数
 时,0 次多项式为一常数  . 定义常数 0 为特殊的零多项式,零多项式是唯一没有次数的多项式. 本书规定零多项式的次数为 -1 . 常用
 . 定义常数 0 为特殊的零多项式,零多项式是唯一没有次数的多项式. 本书规定零多项式的次数为 -1 . 常用  表示多项式. 数域
 表示多项式. 数域  上所有次数小于
 上所有次数小于  的一元多项式构成的集合记为
 的一元多项式构成的集合记为  . 两个多项式
 . 两个多项式  ,
 ,  ,当且仅当两者同次项的系数相等,即
 ,当且仅当两者同次项的系数相等,即  .
 .
设  ,定义加法
 ,定义加法
例如,  ,则
 ,则  . 由于多 项式的系数来自数域
 . 由于多 项式的系数来自数域  ,所以满足加法的结合律和交换律; 零多项式
 ,所以满足加法的结合律和交换律; 零多项式  为加法的零元; 对任一非零多项式
 为加法的零元; 对任一非零多项式  的所有系数取相反数,构成的同次多项式记为
 的所有系数取相反数,构成的同次多项式记为  ,为
 ,为  的负元. 所以
 的负元. 所以  构成一个交换群.
 构成一个交换群.
对任意实数  及
 及  ,定义数乘法
 ,定义数乘法
例如,  ,则
 ,则  . 由于
 . 由于  和多项式的系数均 来自数域
 和多项式的系数均 来自数域  ,故对于
 ,故对于  与多项式系数的乘法满足交换律和结合律,且数乘法对加法满足分配律. 所以
 与多项式系数的乘法满足交换律和结合律,且数乘法对加法满足分配律. 所以  构成一个线性空间.
 构成一个线性空间.
例1.15 区间  上的实值可积函数全体记为
 上的实值可积函数全体记为  . 根据高等数学 [5] 知,
 . 根据高等数学 [5] 知,  .
 .  ,函数的和
 ,函数的和  ,数乘运算为
 ,数乘运算为  . 由于
 . 由于  中的任一
 中的任一  为实值可积函数,即函数值均为实数,而
 为实值可积函数,即函数值均为实数,而  为一个域,故有以下 结论.
 为一个域,故有以下 结论.
[5] 见参考文献[1].
(1) 对于函数的加法,满足交换律、结合律. 零值函数  (0 为零元),
 (0 为零元),  
  ,且
 ,且  ,即
 ,即  有负元. 所以
 有负元. 所以  构成一个交换群.
 构成一个交换群.
(2) 对于数与函数的乘法,  ,满足
 ,满足  
  综上所述,
 综上所述,  构成一个线性代数 (线性空间).
 构成一个线性代数 (线性空间).
定义1.7 设  为定义在非空集合
 为定义在非空集合  上的运算,
 上的运算,  为一代数系统.
 为一代数系统.  且非空,若
 且非空,若  也构成一个代数系统,且运算
 也构成一个代数系统,且运算  
  保持在
 保持在  中的所有性质,称
 中的所有性质,称  为
 为  的一个子代数系统,简称为子代数.
 的一个子代数系统,简称为子代数.
例1.16  是
 是  的子群,
 的子群,  是
 是  的子域,
 的子域,  是
 是  的子域. 但
 的子域. 但  是
 是  的子环而不是
 的子环而不是  的子域,因为数乘法 “.” 在
 的子域,因为数乘法 “.” 在  中不具有在
 中不具有在  中的逆元律. 由于集合的包含关系
 中的逆元律. 由于集合的包含关系  具有传递性,因此子代数的关系也有传递性. 如
 具有传递性,因此子代数的关系也有传递性. 如  也是
 也是  的子域.
 的子域.
由定义1.7 及例 1.15 可知,  且非空,要判断
 且非空,要判断  是否为代数系统
 是否为代数系统  的子代数,需同时考察两个条件
 的子代数,需同时考察两个条件
(1) 运算  对子集
 对子集  是封闭的;
 是封闭的;
(2) 在子集  中,运算
 中,运算  保持在
 保持在  中的所有性质. 然而, 对线性代数 (线性空间) 而言, 有如下定理.
 中的所有性质. 然而, 对线性代数 (线性空间) 而言, 有如下定理.
定理1.1 设  为数域
 为数域  上的一个线性代数,
 上的一个线性代数,  且非空,
 且非空,  为
 为  的一个子线性代数 (子线性空间) 的充分必要条件是加法 “+” 和数乘法 “.”对
 的一个子线性代数 (子线性空间) 的充分必要条件是加法 “+” 和数乘法 “.”对  是封闭的.
 是封闭的.
证明 条件的必要性不证自明, 下面证明充分性. 由运算的封闭性可知, 加法 “+” 的交换律、结合律,数乘法 “.” 的结合律及对加法的分配律在  中都是保持的. 由于
 中都是保持的. 由于  是数域,因此
 是数域,因此  . 又由于数乘法 “.” 对
 . 又由于数乘法 “.” 对  是封闭的,因此
 是封闭的,因此  ,即
 ,即  含有零元. 另外,
 含有零元. 另外,  ,必有
 ,必有  ,有
 ,有  使得
 使得  
  ,即
 ,即  在
 在  中有负元. 如此,
 中有负元. 如此,  构成交换群,加之数乘法所满足的所有性质可知
 构成交换群,加之数乘法所满足的所有性质可知  是一个线性代数 (线性空间),故它为
 是一个线性代数 (线性空间),故它为  的一个子线性代数 (子线性空间).
 的一个子线性代数 (子线性空间).
例1.17 由例 1.15 知,区间  上的实值可积函数全体
 上的实值可积函数全体  对函数的加法“+” 和实数与函数的数乘法 “.” 构成线性代数 (线性空间)(
 对函数的加法“+” 和实数与函数的数乘法 “.” 构成线性代数 (线性空间)(  . 记区间
 . 记区间  上的实值连续函数全体为
上的实值连续函数全体为  ,则
 ,则  且非空[6]. 根据高等数学知识[7],实值连续函数对函数的加法和实数与函数的数乘法是封闭的. 根据定理1.1,
 且非空[6]. 根据高等数学知识[7],实值连续函数对函数的加法和实数与函数的数乘法是封闭的. 根据定理1.1,  是
 是  的一个子线性代数 (子线性空间).
 的一个子线性代数 (子线性空间).
[6] 见参考文献 [1] 第 295 页定理2 后目 1 .
[7] 见参考文献 [1] 第 119 页定理1 .
定义1.8 设两个代数系统  和
 和  均具有
 均具有  个
 个  元运算
 元运算  和
 和  . 若存在
 . 若存在  到
 到  的 “ 1-1 ” 映射
 的 “ 1-1 ” 映射  ,使得对每一对运算
 ,使得对每一对运算  和
 和  ,有
 ,有
即  下
 下  中原像的运算结果对应
 中原像的运算结果对应  中像的运算结果. 称
 中像的运算结果. 称  与
 与  同构.
 同构.  称为
 称为  与
 与  之间的同构映射.
 之间的同构映射.
例1.18 考虑例 1.7 中模 4 的剩余类加群  以及代数系统
 以及代数系统  ,其中
 ,其中  
  . 两者的加法运算表为
 . 两者的加法运算表为
不难验证  也是一个交换群. 建立
 也是一个交换群. 建立  与
 与  的 “ 1-1 ” 映射
 的 “ 1-1 ” 映射  为
 为
则  的加法运算表等价于
 的加法运算表等价于
即  下
 下  中原像的运算结果对应
 中原像的运算结果对应  中像的运算结果. 所以,
 中像的运算结果. 所以,  与
 与  同构.
 同构.
以代数学的观点, 同构的代数系统被视为等同的, 只需研究其中之一, 研究结果适用于所有与之同构的代数系统.
 1.3 Python解法
 1.3 Python解法Python 作为计算机程序设计语言, 受计算机物理结构的限制, 无法表示出完整的整数集  、有理数集
 、有理数集  、实数集
 、实数集  及复数集
 及复数集  . 然而,Python 所模拟的
 . 然而,Python 所模拟的  、
、 、
、 和
 和  在大多数实际应用中可以满足需求.
 在大多数实际应用中可以满足需求.
具体地说, Python 中的整数取值范围仅受计算机内存容量的限制, 而不受 CPU(Central Processing Unit, 中央处理器) 字长的限制. Python 的浮点数的精度和取值范围均受 CPU 字长的限制. 以 float64 为例,其有效数字位数为 15 或 16,取值范围为  
  . 也就是说,Python 以 16 位有效数字的有理数的集合模拟
 . 也就是说,Python 以 16 位有效数字的有理数的集合模拟  乃至
 乃至  . Python 还内置了复数类型
 . Python 还内置了复数类型  来模拟复数集
 来模拟复数集  ,其中
 ,其中  表示虚数单位.
 表示虚数单位.  和
 和  分别表示复数的实部和虚部, 为浮点数. Python 将整数、浮点数和复数统称为数字型数据.
 分别表示复数的实部和虚部, 为浮点数. Python 将整数、浮点数和复数统称为数字型数据.
表 1.1 中的运算可用于所有数字型数据上. 需要说明的是 (4) 商运算 “/” 和 (5) 整商运算 “//”, 前者运算的结果是浮点数, 而后者的运算结果是整数. Python 中, 整数不含小数, 浮点数含小数. Python 根据用户输入的数据或表达式计算结果自动判断其数据类型, 请看下面的例子.
表 1.1 数字型数据的常用运算
| 序号 | 运算 | 运算结果 | 备注 | 
|---|---|---|---|
| (1) | 
 | 
 | |
| (2) | 
 | 
 | |
| (3) | x *  | 
 | |
| (4) | 
 | 
 | |
| (5) | 
 | 
 | 
 | 
| (6) | x % y | 
 | x-  | 
| (7) | x **  | 
 | |
| (8) | 
 | 
 | 单目运算 | 
| (9) | 
 | 
 | 单目运算 | 
例1.19 Python 中数字型数据的算术运算.
程序1.1 Python 的数字型数据
 1  a=4                     #整数
 2  b=3                     #整数
 3  c=a+b                   #整数
 4  print(c,type(c))
 5  c=a/b                   #浮点数
 6  print(c,type(c))
 7  c=a//b                  #整数
 8  print(c,type(c))
 9  b=3.0                   #浮点数
10  c=a+b                   #浮点数
11  print(c,type(c))
12  a=0.1+0.2               #浮点数@(0.1+0.2)@
13  b=0.3                   #浮点数@(0.3)@
14  print(a= =b)            #浮点数相等判断
15  print(abs(a-b)<1e-10)   #浮点数比较
16  a=1+2j                  #复数
17  c=a/b                   #复数
18  print(c,type(c))程序的第 1、 2 行输入整数 4 和 3 (没有小数) 赋予变量 a 和 b. 注意, Python 用 “=”作为为变量赋值的运算符,判断两个数据是否相等的比较运算符为 “  ”. 第 3 行将运算得到的 a 与
 ”. 第 3 行将运算得到的 a 与  的和
 的和  赋予变量
 赋予变量  ,由于加法运算对整数是封闭的 (运算结果仍为整数),故
 ,由于加法运算对整数是封闭的 (运算结果仍为整数),故  亦为整数. 第 4 行输出 c.
 亦为整数. 第 4 行输出 c.
第 5 行将  与
 与  的商
 的商  赋予
 赋予  ,由于整数集
 ,由于整数集  构成环 (参见例 1.11),而不构成域 (参见例 1.13),故对于除法运算不是封闭的. 此时,
 构成环 (参见例 1.11),而不构成域 (参见例 1.13),故对于除法运算不是封闭的. 此时,  自动转换为浮点数. 第 6 行输出
 自动转换为浮点数. 第 6 行输出  .
 .
第 7 行将 a 与  的整数商
 的整数商  (即
(即 )赋予
)赋予  . 此时,运算结果为整数. 故
 . 此时,运算结果为整数. 故  为整数. 第 8 行输出 c.
 为整数. 第 8 行输出 c.
在表达式中既有整数又有浮点数混合运算时, Python 会将表达式中的整数计算成分自动转换成浮点数, 运算的结果自然为浮点数. 第 9 行将浮点数 3.0 (带小数) 赋予 b, 第 10 行将  与
 与  的和
 的和  赋予
 赋予  . 注意此时
 . 注意此时  是整数 (4),
 是整数 (4),  是浮点数 (3.0),故
 是浮点数 (3.0),故  是浮点数. 第 11 行输出 c.
 是浮点数. 第 11 行输出 c.
第 12 行将浮点数 0.1 与 0.2 的和赋予  ,第 13 行将浮点数 0.3 赋予 b. 第 14 行输出相等比较表达式
 ,第 13 行将浮点数 0.3 赋予 b. 第 14 行输出相等比较表达式  的值: 若
 的值: 若  的值与
 的值与  的值相等,该值为 “True”,否则为 “False”. 第 15 行输出小于比较表达式
 的值相等,该值为 “True”,否则为 “False”. 第 15 行输出小于比较表达式  ,即
 ,即  的值: 若
 的值: 若  与
 与  的差的绝对值小于
 的差的绝对值小于  ,该值为 “True”,否则为 “False”. 按常识,这两个判断输出的值应该都是 "True". 但是,
 ,该值为 “True”,否则为 “False”. 按常识,这两个判断输出的值应该都是 "True". 但是,  和
 和  存储的是浮点数,它们只有有限个有效位,在有效位范围之外的情形是无法预测的. a 作为 0.2 (带有无效位) 与 0.3 (带有无效位) 的和, 将两个浮点数所带的无效位通过相加传递给运算结果, 产生了和的无效位, 这可能会产生 “放大” 无效位效应. 事实上, 将其与存储在
 存储的是浮点数,它们只有有限个有效位,在有效位范围之外的情形是无法预测的. a 作为 0.2 (带有无效位) 与 0.3 (带有无效位) 的和, 将两个浮点数所带的无效位通过相加传递给运算结果, 产生了和的无效位, 这可能会产生 “放大” 无效位效应. 事实上, 将其与存储在  中的浮点数 0.3 进行相等比较 (第 14 行),得出的结果是 “False”. 为正确地比较两个浮点数
 中的浮点数 0.3 进行相等比较 (第 14 行),得出的结果是 “False”. 为正确地比较两个浮点数  和
 和  是否相等,采用第 15 行的办法: 计算两者差的绝对值
 是否相等,采用第 15 行的办法: 计算两者差的绝对值  ,考察其值 是否 “很小”——小于一个设定的 “阈值”,此处为
 ,考察其值 是否 “很小”——小于一个设定的 “阈值”,此处为  即
 即  ,此时结果为 “True”. 第 16 行将复数
 ,此时结果为 “True”. 第 16 行将复数  赋予变量
 赋予变量  ,虽然输入的实部 1 和虚部 2 均未带小数,但 Python 会自动将其转换成浮点数. 第 17 行将算得的
 ,虽然输入的实部 1 和虚部 2 均未带小数,但 Python 会自动将其转换成浮点数. 第 17 行将算得的  赋予
 赋予  ,第 18 行输出
 ,第 18 行输出  .
 .
运行程序, 输出
7 <class `int'>
1.3333333333333333 <class `float'>
1 <class `int'>
7.0 <class `float'>
False
True
(0.3333333333333333+0.6666666666666666j) <class `complex'>注意, 本例输出每一个数据项, 同时显示该数据项的类型.
所有的有理数都可以表示成分数. Python 有一个附加的分数类 Fraction, 用于精确表示有理数.
例1.20 有理数的精确表示.
程序1.2 Python 的分数
1  from fractions import Fraction as F     #导入Fraction
2  a = F(4,1)                              #4的分数形式
3  b = F(3)                                #~3的分数形式
4  c = a/b                                 #分数4/3
5  print(c)
6  d = F(1/3)                              #用浮点数初始化分数对象
7  print(d)                                #输出分数近似值
8  print(d.limit_denominator())            #最接近真值的分数分数用 Fraction 的初始化函数创建, 其调用格式为
Fraction(d, v).
参数  和
 和  分别表示分数的分子和分母. 整数的分母为 1,可以省略. 程序中的第 1 行导入 Fraction,为简化代码书写,为其取别名为
 分别表示分数的分子和分母. 整数的分母为 1,可以省略. 程序中的第 1 行导入 Fraction,为简化代码书写,为其取别名为  . 第
 . 第  行分别将 4 和 3 以分数形式赋予变量
 行分别将 4 和 3 以分数形式赋予变量  和
 和  . 前者以分子、分母方式输入,后者省略分母 1 . 第 4 行计算
 . 前者以分子、分母方式输入,后者省略分母 1 . 第 4 行计算  与
 与  的商
 的商  并将其赋予 c. 第 5 行输出 c. 第 6 行用浮点数
 并将其赋予 c. 第 5 行输出 c. 第 6 行用浮点数  初始化分数对象并将其赋予有理数
 初始化分数对象并将其赋予有理数  第 7 行输出
 第 7 行输出  ,它是无穷小数
 ,它是无穷小数  的近似值. 第 8 行调用 Fraction 对象的成员方法 limit_denominator,算得最接近
 的近似值. 第 8 行调用 Fraction 对象的成员方法 limit_denominator,算得最接近  的分数,即
 的分数,即  . 运行程序,将输出
 . 运行程序,将输出
4/3
6004799503160661/18014398509481984
1/3读者可将此与程序1.1 中输出的相应数据项进行对比
Python 中所有的关系运算结果均为布尔值: 非 True 即 False. 其常用关系运算符罗列在表 1.2 中.
表 1.2 常用关系运算符
| 序号 | 运算符 | 含义 | 
|---|---|---|
| (1) | 
 | 严格小于 | 
| (2) | 
 | 小于或等于 | 
| (3) | 
 | 严格大于 | 
| (4) | 
 | 大于或等于 | 
| (5) | 
 | 等于 | 
| (6) | 
 | 不等于 | 
| (7) | in | 元素在集合内 | 
Python 可实现例 1.6 中讨论的布尔代数  . 其中,
 . 其中,  True,False
 True,False  . 分别用运算符 "or""and" 和 "not" 表示 V、
 . 分别用运算符 "or""and" 和 "not" 表示 V、  和
 和  . 关系运算和布尔代数是程序设计中循环和分支语句的 “灵魂”, 在下面的例子中可见一斑.
 . 关系运算和布尔代数是程序设计中循环和分支语句的 “灵魂”, 在下面的例子中可见一斑.
例1.21 例 1.14 中讨论了数域  上的多项式集合
 上的多项式集合  . 我们知道,系数序列
 . 我们知道,系数序列  ,
 ,  确定了
 确定了  . 希望用 Python 根据存储在数组 a 中的系数序列, 输出表示对应的多项式表达式的字符串:
 . 希望用 Python 根据存储在数组 a 中的系数序列, 输出表示对应的多项式表达式的字符串:
其中,  表示多项式的
 表示多项式的  次项系数
 次项系数  在数组 a 中的第
 在数组 a 中的第  个元素值. 约定: 零多项式的系数序列为空“[ ]”.
 个元素值. 约定: 零多项式的系数序列为空“[ ]”.
解 解决本问题需考虑如下几个关键点:
(1) 零多项式需特殊处理;
(2) 常数项,也就是 0 次项不带字符  的幂;
 的幂;
(3) 1 次项的字符  不带幂指数,即输出
 不带幂指数,即输出  ;
 ;
(4) 负系数自带与前项的连接符 “-”, 非负系数需在前面加入连接符 “+”;
(5) 从 2 次项起,各项输出的规律相同,即  .
 .
Python 中的 list 对象和 NumPy 的 array 对象均可作为存储序列的数组, 解决本问题的 Python 代码如下.
pdf 22
程序1.3 构造多项式表达式
 1  import numpy as np                          #导入NumPy
 2  from fractions import Fraction as F         #导入Fraction
 3  def exp(a):                                 #多项式表达式
 4      n=len(a)                                #系数序列长度
 5      s=`'                                    #初始化空字符串
 6      if n==0:                                #零多项式
 7          s=s+`0'
 8      else:                                   #非零多项式
 9          for i in range(n):                  #对每一项
10              if i==0:                        #常数项
11                  s=s+`%s'%a[i]
12              if i==1 and a[i]>=0:            #非负1次项
13                  s=s+`+%s·x'%a[i]
14              if i==1 and a[i]<0:             #负1次项
15                  s=s+`%s·x'%a[i]
16              if i>1 and a[i]>=0:             #非负项
17                  s=s+`+%s·x**%d'%(a[i],i)
18              if i>1 and a[i]<0:              #负项
19                  s=s+`%s·x**%d'%(a[i],i)
20      return s                                #返回表达式
21  a=[1,-2,1]
22  b=[F(0),F(-1,2),F(0),F(1,3)]
23  c=np.array([0.0,-0.5,0.0,1/3])
24  d=[]
25  print(exp(a))
26  print(exp(b))
27  print(exp(c))
28  print(exp(d))本程序中将完成功能的代码组织成一个自定义函数——个可以按名调用的模块. Python 的函数定义语法是
def 函数名 (形式参数表):
    函数定义体本程序中第  行定义的函数名为
 行定义的函数名为  ,形式参数表中仅含的一个参数表示存储多项式系数序列的数组 a. 函数定义体内罗列出函数处理数据的操作步骤. exp 函数中, 第 4 行调用 Python 的 len 函数计算数组 a 的长度,即所含元素个数,并将其赋予变量
 ,形式参数表中仅含的一个参数表示存储多项式系数序列的数组 a. 函数定义体内罗列出函数处理数据的操作步骤. exp 函数中, 第 4 行调用 Python 的 len 函数计算数组 a 的长度,即所含元素个数,并将其赋予变量  . Python 中的字符串类型实现了例 1.5 中讨论的代数系统
 . Python 中的字符串类型实现了例 1.5 中讨论的代数系统  ,其中
 ,其中  为 ASCII 符号集,+ 运算符用于连接两个字符串. Python 的字符串常量是用单引号括起来的字符序列. 第 5 行将表达式串初始化为空字符串. 第
 为 ASCII 符号集,+ 运算符用于连接两个字符串. Python 的字符串常量是用单引号括起来的字符序列. 第 5 行将表达式串初始化为空字符串. 第  行的 if-else 分支语句根据 a 的长度是否为 0 分别处理零多项式和非零多项式. 对于零多项式,第 7 行直接将单字符串 (0 添加到空字符串
 行的 if-else 分支语句根据 a 的长度是否为 0 分别处理零多项式和非零多项式. 对于零多项式,第 7 行直接将单字符串 (0 添加到空字符串  之后. 处理非零多项式的第
 之后. 处理非零多项式的第  行的 for 循环语句,扫描数组 a,处理多项式的每一项. 第 10、 11 行的 if 语句处理常数项,注意第 11 行中连接到
 行的 for 循环语句,扫描数组 a,处理多项式的每一项. 第 10、 11 行的 if 语句处理常数项,注意第 11 行中连接到  尾部的 ‘
 尾部的 ‘ ’ ,
’ ,  称为格式串,串中 “%s’ 称为格式符, 意为以串中指定的格式加载单引号后面的数据项 a[i]. 格式串的一般形式为
 称为格式串,串中 “%s’ 称为格式符, 意为以串中指定的格式加载单引号后面的数据项 a[i]. 格式串的一般形式为
含格式符的串’%(数据项表).
含格式符的串中格式符的个数与数据项表中数据项的个数必须相同, 若数据项表中仅有一个数据项,括号可省略,如第 11 行中的格式串. 常用格式符包含表示字符串的格式符  ,表示十进制整数的格式符
 ,表示十进制整数的格式符  ,表示十进制浮点数的格式符
 ,表示十进制浮点数的格式符  ,等等. 类似地,第 12、 13 行处理非负 1 次项; 第 14、 15 行处理负 1 次项; 第 16、17 行处理以后的非负项; 第 18、 19 行处理负项. 循环结束,第 20 行返回字符串
 ,等等. 类似地,第 12、 13 行处理非负 1 次项; 第 14、 15 行处理负 1 次项; 第 16、17 行处理以后的非负项; 第 18、 19 行处理负项. 循环结束,第 20 行返回字符串  .
 .
程序的第 21 行用 list 对象 a 表示多项式  的系数序列
 的系数序列  ,其中的元 素为整数; 第 22 行用 list 对象
 ,其中的元 素为整数; 第 22 行用 list 对象  表示多项式
 表示多项式  的系数序列,元素类型为 Fraction; 第 23 行用 NumPy 的 array 对象
 的系数序列,元素类型为 Fraction; 第 23 行用 NumPy 的 array 对象  的数组
 的数组  表示多项式
 表示多项式  的系数序列,注意
 的系数序列,注意  的 array 对象可以用 list 对象
 的 array 对象可以用 list 对象  初始化; 第 24 行用空的 list 对象
 初始化; 第 24 行用空的 list 对象  表示零多项式系数序列. 第
 表示零多项式系数序列. 第  行分别调用
 行分别调用  函数和 print 函数输出 a、b、c、d 的表达式. 运行程序,输出
 函数和 print 函数输出 a、b、c、d 的表达式. 运行程序,输出
1-2·x+1·x**2
0-1/2·x+0·x**2+1/3·x**3
0.0-0.5·x+0.0·x**2+0.3333333333333333·x**3
0练习1.6 程序1.3 定义的构造多项式表达式的函数 exp 并不理想: 它将系数为 0 的项也表示在表达式中, 显得有点笨拙. 修改 exp, 在所创建的表达式中忽略系数为 0 的项. (参考答案: 见文件 chapter01.ipynb 中相应的代码)
Python 中整数在计算机内部是按二进制格式存储的, 每一位非 0 即 1 . 因此, 每一位都构成了例 1.6 中的布尔代数  以及例 1.12 中的域
 以及例 1.12 中的域  . 两个整数
 . 两个整数  与
 与  的位运算指的是: 若
 的位运算指的是: 若  与
 与  的二进制位数相同则对应位进行相应的运算,否则对齐最低位,位数少的高位补 0 , 然后对应位进行相应运算. Python 的位运算如表 1.3 所示.
 的二进制位数相同则对应位进行相应的运算,否则对齐最低位,位数少的高位补 0 , 然后对应位进行相应运算. Python 的位运算如表 1.3 所示.
表 1.3 Python 的位运算
| 序号 | 运算 | 运算结果 | 备注 | 
|---|---|---|---|
| (1) | 
 | 
 | 
 | 
| (2) | 
 | x 和 v 按位异或 | 
 | 
| (3) | x&y | 
 | 
 | 
| (4) | 
 | 对  | |
| (5) | 
 | 
 | 
 | 
| (6) | 
 | 
 | 
 | 
需要说明的是:
(1)  并非对整数
 并非对整数  的二进制原码逐位取反,要实现整数原码逐位取反只需对每一位与 1 做异或运算即可 (参见例 1.8);
 的二进制原码逐位取反,要实现整数原码逐位取反只需对每一位与 1 做异或运算即可 (参见例 1.8);
(2) 左移运算表示将整数  向左每移动一个二进制位,右端添加一位 0,即
 向左每移动一个二进制位,右端添加一位 0,即  相当于将
 相当于将  乘
 乘  . 相仿地,
 . 相仿地,  相当于将
 相当于将  除以
 除以  .
 .
int 对象的函数 bit_length 用于计算并返回整数的二进制表达式的长度 (位数), 例如,设 a 中整数为 286,则 a.bit_length() 将返回 9 . 注意,  的二进制表达式为
 的二进制表达式为  . Python 的 bin 函数用于将整数转换成二进制表达式,例如,
 . Python 的 bin 函数用于将整数转换成二进制表达式,例如,  将返回字 符串 ’0b1111111’.
 将返回字 符串 ’0b1111111’.
例1.22 下列代码说明这些运算符的运用.
程序1.4 Python 的位运算
 1  a=17
 2  b=21
 3  print(`a=%s'%bin(a))                    #a的二进制表达式
 4  print(`b=%s'%bin(b))                    #b的二进制表达式
 5  print(`a<<2=%s, %d'%(bin(a<<2),a<<2))   #a左移@2@位
 6  print(`b>>2=%s, %d'%(bin(b>>2),b>>2))   #b右移@2@位
 7  print(`a|b=%s'%bin(a|b))                #a与@b@按位或
 8  print(`a&b=%s'%bin(a&b))                #a与@b@按位与
 9  print(`a^b=%s'%bin(a^b))                #a与@b@按位异或
10  print(`~a=%s'%bin(~a))                  #a的带符号位补码逐位取反后的补码
11  n=a.bit_length()                        #a的二进制表达式的位数
12  b=2**n-1
13  print(`a逐位取反=%s'%bin(a^b))           #a的原码逐位取反程序的第 1、2 行设置两个整数变量,分别初始化为 17 和 21 . 第 3、 4 行分别调用 bin 函数以二进制格式输出  和
 和  . 第 5 行输出
 . 第 5 行输出  左移 2 位后的二进制表达式和十进制表达式.第 6 行输出
 左移 2 位后的二进制表达式和十进制表达式.第 6 行输出  右移 2 位后的二进制表达式和十进制表达式. 第 7 行输出 a 与
 右移 2 位后的二进制表达式和十进制表达式. 第 7 行输出 a 与  的按位或的计算结果. 第 8 行输出
 的按位或的计算结果. 第 8 行输出  与
 与  的按位与的计算结果. 第 9 行输出
 的按位与的计算结果. 第 9 行输出  与
 与  的按位异或的计算结果. 第 10 行对 a 的带符号位的补码逐位取反: a 的原码 10001 的最高位之前还有一个符号位,由于
 的按位异或的计算结果. 第 10 行对 a 的带符号位的补码逐位取反: a 的原码 10001 的最高位之前还有一个符号位,由于  是整数,故符号位为 0,即
 是整数,故符号位为 0,即
正整数的反码和补码就是原码, 逐位取反后得
为一负数 (符号位为 1),其补码是其反码  加 1 的结果,即
 加 1 的结果,即
第  行计算并输出 a 的原码逐位取反的结果: 第 11 行调用整数对象 a 的 bit_length 函数计算 a 的二进制位数并将其赋值给
 行计算并输出 a 的原码逐位取反的结果: 第 11 行调用整数对象 a 的 bit_length 函数计算 a 的二进制位数并将其赋值给  ,第 12 行利用
 ,第 12 行利用  算得
 算得  位均为 1 的二进制整数
 位均为 1 的二进制整数  并将其赋值给 b,第 13 行利用 a 与
 并将其赋值给 b,第 13 行利用 a 与  的按位异或得到对
 的按位异或得到对  的原码逐位取反的结果并将其输出. 运行程序, 输出
 的原码逐位取反的结果并将其输出. 运行程序, 输出
a=0b10001
b=0b10101
a<<2=0b1000100, 68
b>>2=0b101, 5
a|b=0b10101
a&b=0b10001
a^b=0b100
~a=-0b10010
a逐位取反=0b1110定理1.2 给定正整数  ,记
 ,记  ,即
 ,即  是所有
 是所有  位二进制非负整数构成的集合. 代数系统
 位二进制非负整数构成的集合. 代数系统  构成一个环. 其中,
 构成一个环. 其中,  表示
 表示  中整数的按位异或,
 中整数的按位异或,  表示
 表示  中整数的按位与.
 中整数的按位与.
证明 首先,由整数按位运算的意义可知,  . 其次根据例
 . 其次根据例  中元素对按位异或
 中元素对按位异或  满足交换律、结合律. 0 为零元,
 满足交换律、结合律. 0 为零元,  中任一元素
 中任一元素  为自身的负元. 按定义1.3 知,
 为自身的负元. 按定义1.3 知,  构成一个交换群. 根据例 1.6、例 1.8 及例 1.12 知
 构成一个交换群. 根据例 1.6、例 1.8 及例 1.12 知  运算具有交换律、结合律以及
 运算具有交换律、结合律以及  对
 对  具有分配律.
 具有分配律.  为关于
 为关于  的幺元,因为
 的幺元,因为  . 按定义1.4,
 . 按定义1.4,  构成一个环.
 构成一个环.
结合例 1.12 及定理1.2 得知,  时
 时  构成一个域,
 构成一个域,  时
 时  构成一个环.
 构成一个环.
例1.23 TCP/IP(Transmission Control Protocol/Internet Protocol, 传输控制协议/互联网协议) 在互联网中的应用是, 每一个节点都有一个 IP 地址. 对 IPv4 而言, 一个 IP 地址  就是一个 32 位二进制非负整数,即
 就是一个 32 位二进制非负整数,即  . 为方便记,通常将此整数的二进制表达式分成 4 节, 每节 8 位 (1 字节), 用点号 “. ” 隔开. 例如, 一个节点的 IP 地址为整数 3232236047, 其二进制表达式为
 . 为方便记,通常将此整数的二进制表达式分成 4 节, 每节 8 位 (1 字节), 用点号 “. ” 隔开. 例如, 一个节点的 IP 地址为整数 3232236047, 其二进制表达式为
11000000.10101000.00000010.00001111.
第 1 节的 11000000 对应十进制整数 192, 第 2 节的 10101000 对应 168, 第 3 节的 00000010对应 2 , 第 4 节的 00001111 对应 15 . 在文献中为简短计, 常将其记为
TCP/IP 规定每个 IP 地址分成网络号和主机号两部分,并将所有  个 IP 地址分成 A、B、C、D、E 共 5 类. 常用的 A、B、C 这 3 类地址的定义如表 1.4 所示
 个 IP 地址分成 A、B、C、D、E 共 5 类. 常用的 A、B、C 这 3 类地址的定义如表 1.4 所示
表 1.4 3 类地址的定义
| 类别 | 网络号 | 主机号 | 
|---|---|---|
| A | 第 1 字节,取值范围为  | 后 3 字节 | 
| B | 前 2 字节,第 1 字节取值范围为  | 后 2 字节 | 
| C | 前 3 字节,第 1 字节取值范围为  | 最后一字节 | 
路由程序用 “掩码” 来分析 IPv4 地址  的类型、网络号和主机号. 具体介绍如下:
 的类型、网络号和主机号. 具体介绍如下:
(1) 掩码 255.0.0 与  按位与,然后将结果右移 24 位得到地址的第一字节取值,以此判断网络类型;
 按位与,然后将结果右移 24 位得到地址的第一字节取值,以此判断网络类型;
(2) 根据 (1) 得到的网络类型,设置  型网络掩码
 型网络掩码  为 255.0.0.0,B 型网络掩码
 为 255.0.0.0,B 型网络掩码  为 255.255.0.0,C 型网络掩码
 为 255.255.0.0,C 型网络掩码  为 255.255.255.0;
 为 255.255.255.0;
(3)  与掩码
 与掩码  的按位与并将结果右移若干位 (A 类地址右移 24 位,
 的按位与并将结果右移若干位 (A 类地址右移 24 位,  类地址右移 16 位,
 类地址右移 16 位,  类地址右移
 类地址右移  位) 得网络号;
 位) 得网络号;
(4) 对由 (3) 算得的结果逐位取反得  ,计算
 ,计算  与
 与  的按位与结果得到主机号.
 的按位与结果得到主机号.
下列程序对给定的表示 IPv4 地址的整数  判断其网络类型并分析其网络号及主机号.
 判断其网络类型并分析其网络号及主机号.
程序1.5 IPv4 地址分析
 1  def ipAnaly(a):
 2      b32=2**32-1                 #32位1
 3      m=255<<24                   #掩码255.0.0.0
 4      t=(a&m)>>24                 #地址的第1字节
 5      if 1<=t<=126:               #A类地址
 6          t1=`A'                  #地址类型
 7          n=t                     #网络号
 8          m1=m^b32                #掩码255.0.0.0的反码
 9          p=a&m1                  #主机号
10      if 128<=t<=191:             #B类地址
11          t1=`B'                  #地址类型
12          m=(2**16-1)<<16         #掩码255.255.0.0
13          n=(a&m)>>16             #网络号
14          m1=m^b32                #掩码的反码
15          p=a&m1                  #主机号
16      if 192<=t<=223:             #C类地址
17          t1=`C'                  #地址类型
18          m=(2**24-1)<<8          #掩码255.255.255.0
19          n=(a&m)>>8              #网络号
20          m1=m^b32                #掩码的反码
21          p=a&m1                  #主机号
22      return t1, n, p
23  a=2005012608                    #地址119.130.16.128
24  print(ipAnaly(a))
25  a=2282885253                    #地址136.18.16.133
26  print(ipAnaly(a))
27  a=3321996052                    #地址198.1.163.20
28  print(ipAnaly(a))程序的第  行定义用于 IP 地址分析的函数 ipAnaly,该函数仅有一个表示待分析的 IP 地址的整数参数 a. 函数体中,第 2 行用
 行定义用于 IP 地址分析的函数 ipAnaly,该函数仅有一个表示待分析的 IP 地址的整数参数 a. 函数体中,第 2 行用  设置用于原码求反的有 32 位 1 的整数变量 b32; 第 3 行用
 设置用于原码求反的有 32 位 1 的整数变量 b32; 第 3 行用  将掩码
 将掩码  设置为第 1 字节全为 1,其他全为 0,即 255.0.0 .0, 用于分析地址 a 的第 1 字节以判断地址类型; 第 4 行用按位与 a&m 算出 a 中第 1 字节后面 3 字节全为 0,然后
 设置为第 1 字节全为 1,其他全为 0,即 255.0.0 .0, 用于分析地址 a 的第 1 字节以判断地址类型; 第 4 行用按位与 a&m 算出 a 中第 1 字节后面 3 字节全为 0,然后  右移 24 位得 a 的第 1 字节值,将其赋予
 右移 24 位得 a 的第 1 字节值,将其赋予  ; 第 5 个 行、第
 ; 第 5 个 行、第  行、第
 行、第  行的 if 语句分别就判断算得的
 行的 if 语句分别就判断算得的  而得到的 3 种地址类型分析计算网络号
 而得到的 3 种地址类型分析计算网络号  和主机号
 和主机号  . 以第
 . 以第  行的 if 语句为例加以说明,对于另外两个情形,读者可参考代码的解释信息阅读理解. 第 10 行测得地址类型为 B, 第 11 行将字符 ’B’ 赋予 t1; 第 12 行将掩码
 行的 if 语句为例加以说明,对于另外两个情形,读者可参考代码的解释信息阅读理解. 第 10 行测得地址类型为 B, 第 11 行将字符 ’B’ 赋予 t1; 第 12 行将掩码  设为前两字节为 1,其余全为 0,即 255.255.0.0 ; 第 13 行按位与 a&
 设为前两字节为 1,其余全为 0,即 255.255.0.0 ; 第 13 行按位与 a&  计算 a 的前两字节及后两字节为 0 的字节构成的整数,
 计算 a 的前两字节及后两字节为 0 的字节构成的整数,  则将该整数右移 16 位,得到 a 的前两字节的整数值,将其赋予网络号
 则将该整数右移 16 位,得到 a 的前两字节的整数值,将其赋予网络号  ; 第 14 行按位异或
 ; 第 14 行按位异或  计算
 计算  的逐位取反结果,即前两字节为 0 后两字节为 1,将其赋予
 的逐位取反结果,即前两字节为 0 后两字节为 1,将其赋予  ; 第 15 行按位与
 ; 第 15 行按位与  算得 a 的后两字节的值, 将其赋予主机号 p.
 算得 a 的后两字节的值, 将其赋予主机号 p.
第 23、24、25、26 和 27、28 行分别对表示 IP 地址的整数 2005012608(119.130.16.128)、 2282885253(136.18.16.133) 和 3321996052(198.1.163.20) 调用函数 ipAnaly 分析地址的类型、网络号及主机号并将其输出. 运行程序, 输出
(’A’, 119 , 8523904 )
(’B’, 34834, 4229)
(’C’, 12976547, 20)Python 是一门面向对象的程序设计语言, 可以用类的定义方式来自定义代数系统: 定义类中对象 (集合元素) 所具有的属性以及对象间的运算. Python 为顶层抽象类保留了对应各种运算符的虚函数, 我们只需在类的定义中重载所需的运算符虚函数就可使用它们.
例1.24 考虑用 Python 实现例 1.14 中定义的多项式线性空间  .
 .
首先, 定义多项式类 myPoly.
程序1.6 多项式类的定义
 1  import numpy as np                      #导入NumPy
 2  class myPoly:                           #多项式类
 3      def __init__(self, coef):           #初始化函数
 4          c=np.trim_zeros(coef,trim=`b')  #删除高次零系数
 5          self.coef=np.array(c)           #设置多项式系数
 6          self.degree=(self.coef).size-1  #设置多项式次数
 7      def __eq__(self, other):            #相等关系运算符函数
 8          if self.degree!=other.degree:   #次数不等
 9              return False
10          return (abs(self.coef-other.coef)<1e-10).all()
11      def __str__(self):                  #生成表达式以供输出
12          return exp(self.coef)Python 自定义类的语法格式为
class 类名:
    类定义体类定义体内定义所属的各函数. 程序1.6 中,第  行所定义的多项式类名为 “myPoly”. 类定义体中罗列了 3 个函数: 第
 行所定义的多项式类名为 “myPoly”. 类定义体中罗列了 3 个函数: 第  行重载的初始化函数 init、第
 行重载的初始化函数 init、第  行重载的相等关系运算符函数
 行重载的相等关系运算符函数  和第
 和第  行定义的用于输出表达式的函数
 行定义的用于输出表达式的函数  .
 .
Python 的类中的函数分成类函数和对象函数两种. 类函数从属于类, 其调用格式为
类名. 函数名 (实际参数表).
对象函数从属于类的对象, 其调用格式为
对象. 函数名 (实际参数表).
myPoly 类中罗列的 3 个函数都是对象函数, 其定义特征为函数的形式参数表中均含表示对象的 self. 换句话说, 类函数的形式参数表中不含参数 self.
Pyhon 的顶层抽象类中已经定义了部分虚函数, 如几乎每个类都必需的初始化函数 init, 以及各种常用的运算符函数. 重载时这些函数的特征是函数名的首、尾各有两个下画线, 如程序1.6 中重载的 __init__ 、 __eq__ 和 __str__ 函数, 程序员要做的是按需实现这些函数. 普通函数的定义中函数名前后不用如此修饰.
根据例 1.14 中多项式的定义可知  由其次数
 由其次数  及
 及  个系数构成的序列
 个系数构成的序列
所确定,变量是取符号  还是取别的符号无关紧要. Python 中表示序列的数据类型有 list, 还可以使用 NumPy 包中的数组 array 类. 无论是 list 还是 array 的对象, 它们的下标与我们所定义的多项式的系数序列的元素下标一致, 也是从 0 开始的. NumPy 是快速处理数组的工具包, 要使用其中的工具模块需事先导入它, 这就是程序1.6 的第 1 行的任务.
 还是取别的符号无关紧要. Python 中表示序列的数据类型有 list, 还可以使用 NumPy 包中的数组 array 类. 无论是 list 还是 array 的对象, 它们的下标与我们所定义的多项式的系数序列的元素下标一致, 也是从 0 开始的. NumPy 是快速处理数组的工具包, 要使用其中的工具模块需事先导入它, 这就是程序1.6 的第 1 行的任务.
第  行重载的 init 函数中,除了表示创建的多项式对象参数 self,还有一个表示多项式系数序列的数组参数 coef, 该参数既可以是 Python 的 list 对象也可以是 NumPy 的 array 对象. 第 4 行调用 NumPy 的 trim_zeros 函数, 消除参数 coef 的尾部可能包含的若干个 0 . 注意传递给命名参数 trim 的值为 ’
 行重载的 init 函数中,除了表示创建的多项式对象参数 self,还有一个表示多项式系数序列的数组参数 coef, 该参数既可以是 Python 的 list 对象也可以是 NumPy 的 array 对象. 第 4 行调用 NumPy 的 trim_zeros 函数, 消除参数 coef 的尾部可能包含的若干个 0 . 注意传递给命名参数 trim 的值为 ’  ’,表示操作是针对序列 coef 的尾部的. 第 5 行用参数 coef 的数据创建对象自身的 array 型属性 coef. 第 6 行用系数序列的长度 -1 初始化对象的次数属性 degree. 需要注意的是, 当参数 coef 传递进来的是元素均为 0 的数组, 即系数均为 0 的零多项式时,第 4 行操作的结果
 ’,表示操作是针对序列 coef 的尾部的. 第 5 行用参数 coef 的数据创建对象自身的 array 型属性 coef. 第 6 行用系数序列的长度 -1 初始化对象的次数属性 degree. 需要注意的是, 当参数 coef 传递进来的是元素均为 0 的数组, 即系数均为 0 的零多项式时,第 4 行操作的结果  成为一个空 (没有元素) 的数组,第 6 行的操作使得多项式对象的次数 degree 为 -1 . 这与我们在例 1.14 中的约定保持一致.
 成为一个空 (没有元素) 的数组,第 6 行的操作使得多项式对象的次数 degree 为 -1 . 这与我们在例 1.14 中的约定保持一致.
第  行重载的是表示两个多项式是否相等的关系运算符 “
 行重载的是表示两个多项式是否相等的关系运算符 “  ” 的 eq 函数. 该函数有两个参数: 表示多项式对象自身的 self 和另一个多项式对象 other. 其返回值是一个布尔型数据: True 或 False,表示 self 和 other 是否相等. 第 8、 9 行的 if 语句检验两个多项式的次数是否不等, 若不等则返回 False. 第 10 行针对两个次数相同的多项式判断它们是否相等. 我们知道 self 和 other 均具有 NumPy 的 array 对象 coef, 表达式 self.coef-other.coef 表示两个等长数组按对应元素相减得到数组,即
 ” 的 eq 函数. 该函数有两个参数: 表示多项式对象自身的 self 和另一个多项式对象 other. 其返回值是一个布尔型数据: True 或 False,表示 self 和 other 是否相等. 第 8、 9 行的 if 语句检验两个多项式的次数是否不等, 若不等则返回 False. 第 10 行针对两个次数相同的多项式判断它们是否相等. 我们知道 self 和 other 均具有 NumPy 的 array 对象 coef, 表达式 self.coef-other.coef 表示两个等长数组按对应元素相减得到数组,即  . abs(self.coef-other.coef) 则表示数组
 . abs(self.coef-other.coef) 则表示数组  ,而 abs(self.coef-other.coef) <1e-10 则表示数组
 ,而 abs(self.coef-other.coef) <1e-10 则表示数组
其中的每一项都是布尔型数据: 非 True 即 False. (abs(self.coef-other.coef)<1e-10).all() 则表示上述数组中的所有项是否都是 True. 这正是判断两个等长的浮点型数组的对应元素是 否相等,若返回 True,则意味着以  的精确度,断定两个等次多项式相等,否则认为两个 多项式不等.
 的精确度,断定两个等次多项式相等,否则认为两个 多项式不等.
第 11、 12 行重载的对象函数 str 的功能是利用对象自身的 coef 数组数据生成多项式的表达式以供输出时使用: 调用 print 函数输出 myPoly 对象时后台自动调用此函数. 该函数只有一个表示多项式对象自身的参数 self, 在函数体中简单调用我们在程序1.3 中定义的 exp[8] 函数即可. 用下列代码测试  类.
 类.
[8] exp已按练习1.6的要求修改.
程序1.7 测试多项式类
 1  import numpy as np                  #导入NumPy
 2  from fractions import Fraction as F #导入Fraction
 3  p=myPoly(np.array([1,-2,1]))        #用NumPy的array对象创建多项式p
 4  q=myPoly([F(0),F(-1,2),F(0),F(1,3)])#用Python的list对象创建多项式q
 5  r=myPoly([0.0,-0.5,0.0,1/3])        #用list对象创建多项式r
 6  print(p)                            #输出p的表达式
 7  print(q)                            #输出q的表达式
 8  print(r)                            #输出r的表达式
 9  print(p= =q)                        #检测p与q是否相等
10  print(q= =r)                        #检测q与r是否相等程序的第 3 行用整数构成的 array 对象  作为系数序列创建
 作为系数序列创建  对象 p. 注意, 形式上似乎在调用一个与 myPoly 类同名的函数, 实际上在调用 myPoly 的 init 函数创建多项式对象. 第 4 行用 list 对象
 对象 p. 注意, 形式上似乎在调用一个与 myPoly 类同名的函数, 实际上在调用 myPoly 的 init 函数创建多项式对象. 第 4 行用 list 对象  创建分数型系数的多项式 q. 第 5 行用 list 对象
 创建分数型系数的多项式 q. 第 5 行用 list 对象  创建浮点型系数的多项式
 创建浮点型系数的多项式  . 第
 . 第  行分别输出各自的表达式,检测重载的 init 函数和 str 函数. 第 9、 10 两行分别检测
 行分别输出各自的表达式,检测重载的 init 函数和 str 函数. 第 9、 10 两行分别检测  与
 与  是否相等、
 是否相等、  与
 与  是否相等、即检测重载的 eq 函数. 运行程序,输出
 是否相等、即检测重载的 eq 函数. 运行程序,输出
1-2·x+1·x**2
-1/2·x+1/3·x**3
-0.5·x+0.3333333333333333·x**3
False
True接下来, 我们在 myPoly 类中重载多项式的线性运算: 加法运算和数乘运算.
程序1.8 多项式类的线性运算
 1  import numpy as np                                      #导入NumPy
 2  class myPoly:                                           #多项式类
 3      ...
 4      def __add__(self, other):                           #运算符“+”
 5          n=max(self.degree,other.degree)+1               #系数个数
 6          a=np.append(self.coef,[0]*(n-self.coef.size))   #补长
 7          b=np.append(other.coef,[0]*(n-other.coef.size)) #补长
 8          return myPoly(a+b)                              #创建并返回多项式和
 9      def __rmul__(self, k):                              #右乘数k
10          c=self.coef*k                                   #各系数与k的积
11          return myPoly(c)程序第 3 行中的省略号表示程序1.6 中已定义的对象函数. 第  行按例 1.14 定义的多项式加法定义重载运算符 “+” 的函数 add. 该函数的两个参数: self 表示多项式对象本身, other 表示参加运算的另一个多项式对象. 第 5 行调用系统函数 max 计算 self 及 other 中次数的最大者并 +1,将结果赋予
 行按例 1.14 定义的多项式加法定义重载运算符 “+” 的函数 add. 该函数的两个参数: self 表示多项式对象本身, other 表示参加运算的另一个多项式对象. 第 5 行调用系统函数 max 计算 self 及 other 中次数的最大者并 +1,将结果赋予  . 第
 . 第  两行调用 NumPy 的 append 函数利用
 两行调用 NumPy 的 append 函数利用  将两个多项式的系数序列
 将两个多项式的系数序列  和
 和  调整为相同长度. 注意,append 函数的作用是将传递给它的两个参数首尾相接. 第 8 行用 a 与
 调整为相同长度. 注意,append 函数的作用是将传递给它的两个参数首尾相接. 第 8 行用 a 与  按元素求和得到的序列创建 myPoly 对象并将其返回.
 按元素求和得到的序列创建 myPoly 对象并将其返回.
我们已经看到重载的相等运算符 “  ” 函数实际上是函数
 ” 函数实际上是函数  ,它是自身对象 self 的函数. 表达式
 ,它是自身对象 self 的函数. 表达式  的作用实际上是调用
 的作用实际上是调用  的
 的  函数,
 函数,  扮演了第一个参数
 扮演了第一个参数  是传递给
 是传递给  的 other 参数. 此处重载的加法运算符 “+” 函数的参数意义也是如此. 然而,数乘运算就不能简单地重载乘法运算符 “*” 函数 mul, 因为参加运算的一个是多项式 (参数 self), 另一个是数 k. 换句话说,self 参数表示的是多项式
 的 other 参数. 此处重载的加法运算符 “+” 函数的参数意义也是如此. 然而,数乘运算就不能简单地重载乘法运算符 “*” 函数 mul, 因为参加运算的一个是多项式 (参数 self), 另一个是数 k. 换句话说,self 参数表示的是多项式  ,调用时就应写成
 ,调用时就应写成  ,这不符合大多数人的习惯. 因此,程序1.8 的第
 ,这不符合大多数人的习惯. 因此,程序1.8 的第  行重载的是 “右乘” 运算符 “*” 函数 rmul,调用时多项式可写在运算符的右边:
 行重载的是 “右乘” 运算符 “*” 函数 rmul,调用时多项式可写在运算符的右边:  . 该函数的第一个参数 self 表示多项式对象,它作为右运算数,第二个参数表示左运算数
 . 该函数的第一个参数 self 表示多项式对象,它作为右运算数,第二个参数表示左运算数  . 第 10 行用
 . 第 10 行用  按元素乘 self 的系数序列 coef,将结果赋予 c. 第 11 行用
 按元素乘 self 的系数序列 coef,将结果赋予 c. 第 11 行用  创建结果多项式并将其返回.
 创建结果多项式并将其返回.
例1.25 下列代码用于测试多项式的线性运算.
程序1.9 测试多项式的线性运算
 1  import numpy as np                  #导入@NumPy@
 2  from fractions import Fraction as F #导入@Fraction@
 3  p=myPoly(np.array([1,-2,1]))        #用@NumPy@的@array@对象创建多项式@p@
 4  q=myPoly([F(0),F(-1,2),F(0),F(1,3)])#用@Python@的@list@对象创建多项式@q@
 5  k=1.5
 6  print(p+q)
 7  print(k*q)运行程序, 输出
1-5/2·x+1·x**2+1/3·x**3
-0.75·x+0.5·x**3将程序1.6、程序1.8 定义的 myPoly 类代码写入文件 utility.py, 以便调用.
练习1.7 利用 myPoly 类中定义的加法运算符函数和数乘运算符函数重载减法运算符函数 __sub__ , 并加以测试.
(参考答案: 参见文件 chapter01.ipynb 中相应代码)