《数据结构与算法Python语言描述》裘宗燕 笔记系列
该系列笔记结合PPT的内容整理的,方便以后复习,有需要的朋友可以看一下。
源码重新整理了
地址:https://github.com/StarsAaron/DS/tree/master
抽象数据类型
抽象数据类型的思想:
采用某种数据形式表示所需的数据“类型”
并定义一组操作,实现对有关数据对象的所需操作
用元组实现有理数(如上学期的示例),可以看作抽象数据类型的一种实现。但这种实现不够理想,因为它不抽象
只是元组的具体应用,不是新类型,不具有类型的特点
Python 内置类型是“抽象数据类型”,符合上面的思想
许多 Python 标准库包实现某种数据类型,它们像内置类型一样是实实在在的抽象,如标准库手册第 8 节介绍的各种数据类型包
在写复杂程序时,如果能自己创建与内置类型性质和使用方式类似的
“用户定义类型”,有可能把程序组织得更好
这样定义类型是建立“数据抽象”(与 [计算] 过程抽象对应),定义的一个抽象
抽象数据类型和 Python 的 class
Python 提供了支持定义数据抽象的机制
定义数据类型的标准 Python 机制是 class 定义(类定义)定义一个类(class)就是定义一个新类型
定义好的类可以像内置类型一样使用,包括生成类的实例(类的实例称为对象,object,在前面讨论中反复提到)
在类定义里,可以为本类的实例定义一组相关操作
对一个类的实例对象,可以使用类里定义的这些操作
Python 的基本系统就是基于类和对象构造起来的
class 是 Python 语言 基本的概念,许多重要问题需要用 class 及其性质解释
例如:Python 内部类型的性质,异常和异常处理等
标准库包的抽象数据类型都是用 class 定义的
类与面向对象的编程
定义类,生成类的对象,基于这种对象组织和描述计算,这一套做法称为面向对象编程(Object Oriented Programming,OOP)
OOP 是软件领域 重要的技术,有利于分解系统的复杂性
许多复杂软件系统都是用 OOP 技术开发的
Python 也称为是一种面向对象的编程语言
类是在基本计算结构(表达式/语句/函数定义)之上的高级结构
语法:
class C : # 定义一个以 C 为名字的类语句块
或
class C (B) : # 基于已有类 B 扩充定义一个新类 C 语句块裘宗燕,2014-9-24-/68/
类定义中的程序块里通常是一组函数定义
用类定义数据抽象时,这种函数定义描述了该类的对象的行为
可以定义函数去创建、操作、生成该类的对象
下面考虑定义一个有理数类
给这个类取名 rational
该类的对象是具体的有理数
需定义各种有理数运算(操作),以便在程序里方便地使用它们
rational 类的定义: class rational :
__init__(self, num, den) : # __init__ 函数创建类的对象
self.num = num # 第一个参数 self 表示被创建对象
self.den = den # 通过 self 和圆点给对象的成分赋值
... ... # 成分的名字根据需要选择
类定义实例:有理数类
名字为 __init__ 的函数称为它所在类的构造函数
创建这个类的对象时,自动调用这个函数
在创建类的对象时,不需要为 __init__ 的第一个参数 self 提供值,但需要为其他参数提供值
r = rational(2,3) # 建立一个 rational 类对象,2 和 3 是其成分
定义一个操作输出有理数对象
设计输出,采用分子/分母的形式,函数取名 print
函数定义(放在类定义的程序块里) def print(self) : print(self.num, "/", self.den)
函数的使用
r.print()
为类的对象定义操作(称为方法)和使用的要点:
方法的定义写在类的程序块里
方法的第一个参数表示操作对象,通常用 self 作为参数名,方法体里用该参数加圆点的形式引用对象的成分(取值或赋值)
当变量的值是本类的对象时,用圆点加方法名的形式调用方法,并为方法(除第一个参数外)的其他参数提供实参
定义一个到 str 类型的转换函数:
def __str__(self) :
return str(self.num) + "/" + str(self.den)
人们在定义类时,经常为其定义一个到 str 的转换函数
主要用途是从对象生成字符串,以便输出或在其他地方使用
有理数加法,例如用 plus 作为方法名:
def plus(self, another) :
den = self.den * another.den
num = (self.num * another.den + self.den * another.num) return rational(num, den)
注意:这里调用 rational 构造新有理数对象,而不直接构造 tuple
下面会看到这种统一描述带来的收益
使用:
r1 = r.plus(rational(3, 4)) r2 = r1.plus(r)
我们可能觉得用这种形式写计算不自然, 好能用内置数类型的运算的写法,用 + - * / 等运算符
在 Python 里可以做这件事,需要用一些特殊的方法名裘宗燕,2014-9-24-/72/
表示运算符的特殊名见语言手册 3.3.7 节,包括:
object.__add__(self, other)
object.__sub__(self, other)
object.__mul__(self, other)
object.__truediv__(self, other)
object.__floordiv__(self, other)
object.__mod__(self, other)
object.__pow__(self, other[, modulo])
... ...
模拟算术运算的加法方法定义:
def __add__(self, another) :
den = self.den * another.den
num = (self.num * another.den + self.den * another.num)
return rational(num, den)
r3 = r2 + r1
使用这个 rational 类建立对象,做一些运算
>>> x = rational(3,5) >>> x = x + rational(7,10) x.print()
65 / 50
易见,定义的类没做分数化简,会导致分子分母变得很大
化简的数学基础很清楚,但怎么修改程序?
可以修改实现加法的方法,加入与化简有关的语句但还有减法、乘法、除法等。这样需要做许多重复工作
另一可能性是修改构造函数如果所有有理数都是用构造函数建立,一个修改就能解决所有问题
考虑所有可能情况(如分子/分母是负数,错误值等),有下面定义:
def __init__(self, num, den = 1) :
if type(num) != int or type(den) != int : raise TypeError
if den == 0 :
raise ValueError
sign = 1 if (num < 0) : num, sign = -num, -sign if (den < 0) :
den, sign = -den, -sign
g = gcd(num, den) self.num = sign * (num//g) self.den = den//g
这里还需要一个求 大公约数的函数,可以是全局定义的,也可以定义在有理数类的里面
类定义实例:有理数类
增加其他运算已经没有任何困难了,请自己补充完整
剩下的问题一方面是数学,大家都熟悉
另一方面是具体运算的编程,可以参考 __add__ 的实现
把 rational 类的完整定义放入一个文件,作为一个模块
如果需要用有理数,import 这个模块,类型 rational 就有了定义
可以用 rational 建立有理数对象,用它提供的功能操作有理数
从使用的角度看,这个 rational 与内置的 int, list 等类型没有差别
对于 class 定义,还有一些功能和规定
有关情况可查看语言手册,其他参考书
上面讨论的是 规范的使用,基本满足本课程的需要,后面会根据情况考虑