(一)、线性结构优劣势
我们往往以数组作为线性结构的经典例子,
学习过的高级语言中,数组往往具有以下特征。
数组的特征
1.数组内放置同类型数据如:str数组
2.利用数组的头部地址可以以O(1)的效率找到各个索引位置,极其高效
解释:
数组的头部地址已知,并且数组中的每个元素的类型和大小都是固定的
(比如每个元素是4字节的整数)
那么可以通过头部地址和元素大小来计算数组中任意元素的地址。
如a[0]+4byte就得到了a[1]的内存地址。
这种通过计算找到各个索引的地址,放在计算机中是极其容易的事情,
因此数组的方便之处就在于其索引进行线性的查找
且时间复杂度极低的O(1)----常数级。
优势
线性的优势:单个索引处的增删、改查可以快速计算位置,达到O(1)的极高效率。
劣势
线性的缺点:
1.插入和删除带来整体移动:
在数组较前位置的索引处"插入或者删除元素",数组整体移动了前/后,被迫移动一个索引位置,因此插入和删除操作大部分时候的时间复杂度变为O(n)
解释:插入删除则最糟需要n个位置的移动
2.数组长度扩容问题
数组的长度是固定的,到达边界时候下一个元素会产生扩容问题,
数组扩容的具体做法:
新建对象开辟内存空间,占据原来大小的二倍的内存空间,将旧对象的内容拷贝到新对象内。
注:另外,
这种扩容的底层是以"位运算"操作实现的如:位运算符">>"
向右移动一位,实现的是在"位"的层面上扩大二倍。
(二)、ADT
1.什么是ADT(Abstract Data Type抽象数据类型)
ADT是抽象数据类型,是一种高层次抽象的"类"(由class声明产生的抽象type)。
注:ADT, 即"抽象类"
ADT的构成:
这个类的内部列出了各个数据结构内部的结构,具体涉及的方法都是"抽象方法",不去实现"抽象方法"内部细节逻辑,而只展示接口(函数名,需要的参数,返回值).
为了理解ADT,以代码为示例:
from abc import ABC, abstractmethod
class ListADT(ABC):
'''
该ListADT类是展示模仿List类制作一个List的ADT
以助于他人理解和使用List的数据结构
'''
@abstractmethod
def append(self, item):
"""将元素添加到列表末尾"""
pass
@abstractmethod
def insert(self, index, item):
"""在指定索引处插入元素"""
pass
@abstractmethod
def remove(self, item):
"""移除指定元素"""
pass
@abstractmethod
def pop(self, index=-1):
"""弹出并返回指定索引处的元素,默认是最后一个元素"""
pass
@abstractmethod
def get(self, index):
"""返回指定索引处的元素"""
pass
@abstractmethod
def size(self):
"""返回列表的元素个数"""
pass
@abstractmethod
def is_empty(self):
"""检查列表是否为空"""
pass
2.在python语言的开发者的代码角度解释一个ADT的构成:**
<1.>针对python语言的使用者—ABC、abstractmethod类的介绍:
ABC(abstract base classes )抽象基类,该基类仅用于其他类继承,继承后可以创建抽象方法。
abstractmethod,抽象方法类,该类引入之后可以在python中编写ADT内部的抽象方法。
<2.>ADT的基本构成:
import abc from ABC,abstractmethod
class ArraryADT(ABC):
'''一个抽象类的基本构成'''
@abstractmethod
def 方法1 (参数列表...)
return ...
@abstractmethod
def 方法2 (参数列表...)
return ...
@abstractmethod
def 方法3 (参数列表...)
return ...
3.为什么要设计出ADT这种抽象数据类型,而不是直接实现呢?设计它有什么用?
< 1.>(抽象思想和方便维护)
首先,ADT它将一个类进行模块化,抽象化,是面向对象思想的展现,将程序员的抽象模块化面向对象的思想再次加深。
可以培养程序员在"不改变数据结构接口"的情况下,"替换"或"改进"其底层实现的这种能力。
< 2.>对于自身作为数据结构的设计者而言(有条不紊的设计流程):
加深自身对于ADT的理解,能让开发者对于类的构思时,由整体入手,抽象出具体需要实现的功能进行宏观构思,而不用先关注于内部的逻辑无从下手。
< 3.>对于调用他人接口实现自己代码的过程而言(只了解浅层的快速入手):
写程序想调用他人的功能时,查看别人的ADT可以迅速的上手他人的接口,完全不用了解他人如何实现具体功能,只需理解类内各个接口的构成和使用,供人快速调用接口。
< 4.>对于提升自己看源码而言(欲了解深层时候的由浅入深):
作为一名初级的程序员,我们看待平常学过的熟悉的数据结构array可能不屑于去了解,又对于涉及较浅而总是不敢涉足的"树结构、图结构的领域总是不敢下手去查看源码"。
但是如果我们先了解它的ADT构成,可能是极为容易上手的,然后再去细致啃下来它内部的实现。
(三)、线性结构的ADT和实现
为了让自己掌握线性结构,先写好ADT再实现它
from abc import ABC, abstractmethod
class ArrayADT(ABC):
'''这里是以代表线性结构的Array来写一个ADT'''
@abstractmethod
def __init__(self,size):
pass
@abstractmethod
def __setitem__(self,key,value):
pass
@abstractmethod
def __getitem__(self,key):
pass
@abstractmethod
def __iter__(self):
pass
实现ADT
from abc import ABC,abstractmethod
class ArrayADT(ABC):
'''这里是以代表线性结构的Array来写一个ADT'''
@abstractmethod
def __init__(self,size):
pass
@abstractmethod
def __setitem__(self,key,value):
pass
@abstractmethod
def __getitem__(self,key):
pass
@abstractmethod
def __iter__(self):
pass
class Array(ArrayADT):
'''这里是对于ADT的具体实现'''
def __init__(self,size):
self.__size=size
self.__item=[None]*size
#用列表来创建一个更长的列表,隐式地把一个list列表充当Array数组。
#这里用list的乘法复制,有点消耗存储资源。
self.__length=0
def __setitem__(self,key,value):
self.__item[key]=value # __item属性派上用场了
self.__length+=1
def __getitem__(self,key):
return self.__item[key]
def __len__(self):
return self.__length
def __iter__(self):
for value in self.__item:
yield value
# yield 是返回一个迭代器对象,它会暂停并返回迭代器对象中的一个值,是一个值一个值地返回。
#下面进行测试
if __name__=='__main__':
a1=Array(4)
# 建立长度为4的空数组
a1[0]=("小明")
a1[1]=("小红")
a1[2]=("小亮")
print(a1[0])
print(a1[1])
print(a1[2])
print(len(a1))
for v in a1:
print(v)
(四)、在(三)ADT的编写中总结ADT的大致编写思路:
1.__init__(self,a,b)
按惯例而言,__init__的内部不应该设置return值,
不应该由return显式地返回值,而应该对属性们隐式地进行初始化。
因此它编写的惯例是:
<1>首先要完成对参数列表的初始化赋值,如
self.__size=size
对象的实例化通常需要借助其它类作为实例
self.__item=[None]*size
# 很多数据结构的构造方法都需要在里面借助其它类对象初始化实例对象)
<2>设置有必要的隐藏属性,以供本类以及类内的其它方法使用。
self.__length=0 # 默认的数组大小为0
# 很多数据结构的构造方法的初始的一些属性设为0,比如length、size、next()
2.其它的方法,如:def __setitem__()
改写时候会改写到很多的特殊方法如:__setitem__
此处是刚好是因为这几个都改写了根类object的方法,才使用"__ __".
如 for v in a1:
print(v)
对应的就是自动调用__iter__的特殊方法用于迭代。
如:a[0]="小明",这种字样,
对应的就是自动调用__setitem__的特殊方法。
如:print(a[0])
对应的就是自动调用__getitem__的特殊方法。
除了构造方法之外,想写return就写return,视需求而定。