数据结构与算法(python)
1.绪论
1.1 计算机问题求解
1.2 实例问题解决
(1)问题基本分析
(2)提升严谨度
(3)算法的精华与python的描述
(4)讨论
①
②
1.3 算法与算法分析
(1)问题、问题实例与算法
(2)算法的性质
(3)算法的各种描述
(4)算法与程序
(5)算法设计与分析
①算法设计模式
(6)算法分析
①基本循环程序
(7)python程序计算的代价(复杂度)
①时间开销
② 空间开销
1.4 数据结构
(1)信息、数据、数据结构
典型的数据结构
(2)python对象与数据结构
2. 抽象数据类型与python类
2.1 抽象数据类型
ADT(Abstract Data Type)是一种思想,也是一种组织程序的技术,主要包括:
1、围绕着一类数据定义程序模块。
2、模块的接口和实现分离。
3、在需要实现时,选择一套合适的机制,采用合理的技术,实现这种ADT的功能,包括具体的数据表示和操作
2.2 python的类
本节主要讲python利用class实现抽象数据类型
class Rational_number():
def __init__(self,num,den=1):
self.num=num
self.den=den
def plus(self,another):
den=self.den*another.den
num=self.den*another.num+self.num*another.den
# 求最大公约数,保证可以被约到最简形式
max=self.greatest_common_divisor(num,den)
den=den/max
num=num/max
# int()确保输出的是整数
return Rational_number(int(num),int(den))
# 求两个数的最大公约数
def greatest_common_divisor(self,a,b):
if a<b:
temp=a
a=b
b=temp
c=a%b
while c!=0:
a=b
b=c
c=a%b
return b
def print_number(self):
# str(实数)可以将实数转化为字符串
print(str(self.num)+"/"+str(self.den))
a=Rational_number(3,2)
b=Rational_number(5,2)
a.plus(b).print_number()
代码解释说明:
class Rational_number():
def __init__(self,num,den=1):
if not isinstance(num,int) or not isinstance(den,int):
raise TypeError
if den==0:
raise ZeroDivisionError
sign=1
if num<0:
num,sign=-num,-sign
if den<0:
den,sign=-den,-sign
g=Rational_number._greatest_common_divisor(num,den)
# 在python3.0中,"/"表示浮点数除法,返回浮点结果;"//"表示整数除法,返回整数结果。
self._num=sign*(num//g)
self._den=den//g
# 求两个数的最大公约数
@staticmethod
def _greatest_common_divisor(a,b):
if b==0:
a,b=b,a
while a!=0:
a,b=b%a,a
return b
下面是完整的程序,此时的有理数类以及和python系统内部的类型没有什么差别了,地位和用法相同,python标准库的一些类型就是如此定义的。
class Rational_number():
def __init__(self,num,den=1):
if not isinstance(num,int) or not isinstance(den,int):
raise TypeError
if den==0:
raise ZeroDivisionError
sign=1
if num<0:
num,sign=-num,-sign
if den<0:
den,sign=-den,-sign
g=Rational_number._greatest_common_divisor(num,den)
# 在python3.0中,"/"表示浮点数除法,返回浮点结果;"//"表示整数除法,返回整数结果。
self._num=sign*(num//g)
self._den=den//g
def __add__(self,another):
den=self._den*another._den
num=self._den*another._num+self._num*another._den
# 求最大公约数,保证可以被约到最简形式
max=Rational_number._greatest_common_divisor(num,den)
den=den//max
num=num//max
return Rational_number(num,den)
# 定义相等
def __eq__(self, other):
return self._den*other._num==self._num*other._den
# 定义大于
def __gt__(self, other):
return self._den * other._num < self._num * other._den
# 定义小于
def __lt__(self, other):
return self._den * other._num > self._num * other._den
# 求两个数的最大公约数
@staticmethod
def _greatest_common_divisor(a,b):
if b==0:
a,b=b,a
while a!=0:
a,b=b%a,a
return b
def get_num(self):
return self._num
def get_den(self):
return self._den
def __str__(self):
return str(self._num)+"/"+str(self._den)
def print_number(self):
# str(实数)可以将实数转化为字符串
print(self._num,"/",self._den)
a=Rational_number(3,4)
b=Rational_number(5,2)
(a+b).print_number()
print((a+b).__str__())
t=type(a)
print(t)
print(a>b)
2.3 类的定义与使用
举一个例子,假设定义的类要维护一个计数器,记录运行程序中创建该类的实例对象的个数,如下:
class Countable():
counter=0
def __init__(self):
Countable.counter+=1
@classmethod
def get_count(cls):
return Countable.counter
x=Countable()
b=Countable()
z=Countable()
# 输出3,表示有3个实例
print(Countable.get_count())
类继承的主要作用有两个:一个是基于已有的类定义新类,通过继承的方式复用已有类的功能,重用代码;另一个作用更为重要,建立一组类之间的继承关系,从而更好地组织代码,构建复杂的程序(如接口类)。派生类可以原封不动地使用基类的所有方法和属性,也可以修改之,还可以扩展新功能和属性。
假设类C是类B的派生类,则C类的对象也可以看成是B的对象,人们经常希望在类B的实例对象的上下文中可以使用类C的实例对象,这就是面向对象编程的最重要的一条规则:替换原理。一个类可能是其他类的派生类,它有可能被用作基类去定义新的派生类,从而在程序中形成了一种层次结构。Python有一个最基本的内置类object,其中定义了一些所有的类都需要的功能,如果一个类没有说明基类,则该类就自动以object为基类。也就是说,object是用户自定义基类的直接或者派生类的基类。
基于已有类BaseClass定义派生类的语法形式是:class <类名> (BaseClass,…):
列在类名后面括号中的参数就是制定的基类。Python内置函数issubclass检测两个类是否具有继承关系,如果cls2是cls1的直接或者间接基类,则表达式issubclass(cls1, cls2)返回True,否则返回False。举一个简单的例子,我们定义一个自己的字符串类:
class mystr(str):
pass
s=mystr(1234)
print(issubclass(mystr,str))
print(isinstance(s,mystr))
print(isinstance(s,str))
结果均为True。
派生类一般要重新定义__init__函数,完成该类实例的初始化,常见的情况是要求派生类的对象可以作为基类的对象,用在要求基类对象的环境中,在使用这种对象时,可能调用派生类自己的方法,也可以调用基类继承的方法,因此这种实例对象就应该包含基类的所有属性,完成这一初始化工作就是直接调用基类的__init__()方法,也就是说,在派生类的__init__()函数中,一般要这样写:
class DerivedClass(BaseClass):
def __init__(self,...):
BaseClass.__init__(self,...)
....# 初始化函数的其他操作
在调用基类的初始化方法时,必须明确写出基类的名字,不能从self出发去调用。在调用基类的__init__时,必须把表示本对象的self作为调用的第一个实参。可能还需要传一些其他参数,这个调用完成了派生类实例中属于基类那部分属性的初始化工作。
在派生类中覆盖基类的方法时,也经常希望新函数是基类同名函数的扩展,也就是说要让新函数包含覆盖函数的已有功能,处理方法和__init__类似,也是在新函数的定义中,用BaseClass.methodName(…)形式调用基类的方法。在这种调用中,注意要把本对象的self作为函数调用的第一个实参。(实际上,可以用这种形式调用基类的任何函数)
如果从一个派生类的实例对象出发去调用方法,python解释器需要确定应该调用哪个函数(基类还是派生类),查找过程从实例对象所属的类开始,如果在这个类里面找到,则采用相应的函数定义,没有找到则去基类找。这样的话,意味着派生类可以覆盖基类的同名方法。
看下面的代码,假设B是C的基类,显然创建B类的实例对象后,调用x.f()会打印出B.g called。但是如果创建了一个C类的实例对象并且调用了y.f()怎样?
# code showing dynamic binding
class B:
def f(self):
self.g()
def g(self):
print("B.g called")
class C(B):
def g(self):
print('C.g called')
x=B()
x.f()
y=C()
y.f()
由于C类没有定义f函数,因此y.f()实际调用的是类B里的f,由于在f的定义中出现了self.g(),那么如何确定应该调用的函数g呢?由于正文中,f的定义出现在B里,类B的self类型是B,从这个类型去查找g,则执行的是B中的g函数,这就是静态绑定(静态约束),但python和大多数面向对象的语言一样,基于方法调用时self表示的那个实例对象的类型去寻找g,也就是说,实例对象是C类的,在函数执行过程中出现self,一律视为C类,自然要去C类中寻找g,这就是动态约束,也就是动态绑定,输出C.g called。在程序设计领域,这种通过动态约束确定调用关系的函数称为虚函数。
python提供了一个内置函数super,把它用在派生类的定义里,就是要求从这个类的直接基类开始做属性检索。采用super函数而不直接写具体基类的名字,产生的查找过程更加灵活,如果直接写基类的名字,无论在什么情况下执行,总是调用该基类的方法,如果写super(),Python解释器会根据当前类的情况去寻找相应的基类,自动确定究竟该使用哪个基类的属性。
函数super有几种使用方法,简单的是不带参数的调用形式,如super().m(…)。如果在一个方法函数的定义中出现这个调用语句,则执行到这里,python就会从这个对象所属的基类开始,按照上面的属性规则去查找函数m,下面一段代码:
class C1:
def __init__(self,x,y):
self.x=x
self.y=y
def m1(self):
print(self.x,self.y)
class C2(C1):
# 下面的两行代码可以省略,省略的话,自动调用C1的初始化函数
def __init__(self,a,b):
C1.__init__(self,a,b)
def m1(self):
super().m1()
print("Some special service.")
x=C2(5,2)
x.m1()
如果执行C2里的m1,python会从C2的基类中开始找m1,也就是从C1中查找。由于C1里面有m1的定义,因此最终调用的是C1的函数m1,显然这种形式的super函数调用(并进而调用基类的某方法)只能出现方法函数的定义里面,在实际调用时,当前实例将作为被调用函数的self实参。
函数super的第二种使用是super(C, obj).m(…),这种写法要求从指定的类C的基类开始查找函数属性m,调用里出现的obj必须是类C的一个实例。Python解释器找到函数m后将用obj作为函数的self实参,这种写法可以出现在程序的任何地方,不限于函数内。
版权声明:本文为CSDN博主「天风海雨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhang669154/article/details/79392561
2.4 python异常
3. 线性表
3.1 线性表的概念和表的抽象数据类型