一、数组数据结构
python列表的底层数据结构就是数组。通常数组的长度和容量在创建的时候就固定下来了
1、随机访问和连续内存
数组索引是一个随机的访问操作,不管数组多大,它访问第1项和最后1项所需的时间是相同的。
python数组中的索引操作有两个步骤:
[1]、获取数组内存块的基本地址
[2]、给这个地址加上索引,返回最终的结果
常量时间的随机访问要求数组必须用一段连续的内存地址来表示,而当在数组上实现其他操作时,这会需要一些代价
2、静态内存和动态内存
在FORTRAN和Pascal这样层级的语言,数组是静态数据结构,数组的长度和大小都是在编译时确定的。
像Java和C++这样的现代语言就是动态数组,像静态数组一样,动态数组占据了连续的内存块并支持随机访问。在运行时并不需要知道动态数组的长度,在实例化的时候指定一个动态数组的长度,python的Array类的行为方式是类似的。
有一种方法可以在运行时根据应用程序的数据需求来调整数组的长度,可以采用如下三种形式:
[1]、在程序开始的时候创建一个具有合理默认大小的数组
[2]、当这个数组不能保存更多的数据的时候,创建一个新的、更大的数组,并且从旧的数组转移数据项
[3]、当数组似乎存在浪费的内存的时候(有一些数据已经被应用程序删除了),以一种类似的方式来减小数组的长度
对于python列表来说,这些调整都是自动进行的
3、物理大小和逻辑大小
数组的物理大小(长度):数组单元的总数,或者说是在创建数组的时候,用来指定其容量的数字。
数组的逻辑大小:当前可供应用程序使用的项的数目。
[1]、若逻辑大小为0,数组为空。也就是说数组不包含数据项
[2]、否则,在任何给定的时间,数组中最后一项的索引都是其逻辑大小减去1
[3]、若逻辑大小等于物理大小,数组已经被数据填满了
二、数组的操作
1、增加数组的大小
python的list类型通过调用insert或append方法来执行增加数组的大小
[1]、创建一个新的、更大的数组
[2]、将数据从旧的数组复制到新的数组中
[3]、将旧的数组变量重新设置为新的数组对象
2、减小数组的大小
当pop方法导致内存空间浪费超过某一个阈值时(如逻辑大小不足物理大小的四分之三), python的list类型就会减小数组的大小,该操作与增加数组的大小的过程时相反的
[1]、创建一个新的、更小的数组
[2]、将数据从旧的数组复制到新的数组中
[3]、将旧的数组变量重新设置为新的数组对象
3、在数组中插入一项
[1]、在尝试一次插入或增加数组物理大小之前,必要的话,先检查可用空间
[2]、从数组的逻辑末尾开始,直到目标索引位置,将每一项都向后移动一个单元
[3]、将新的项赋值给目标索引位置
[4]、将逻辑大小增加1
4、从数组中删除一项
与插入一项是相反的过程
[1]、从紧跟目标索引位置之后位置开始,到数组的逻辑末尾,将每一项都向前移动一位
[2]、将逻辑大小减去1
[3]、检查浪费的空间,必要的话,将数组的物理大小减去1
5、复杂度权衡:时间、空间和数组
在任意位置插入和删除比在逻辑末尾插入和删除可能会慢上一个量级。
装载因子:数组中存储的项的数目除以数组的容量。
三、二维数组
二维数组或网格,要访问其中的一项,使用两个下标来指定其行和列的位置
1、处理网格
除了下标变成了2个,网格还必须实现返回行号和列号的两个方法。---getHeight、getWidth
2、创建并初始化网格
假设有一个用于二维数组的Grid类,参数有三个,高度、宽度和最初的填充值
3、定义Grid类
使用一个数组表示网格,顶层的数组长度等于网格中行的数目,顶层数组的每一个单元格也都是一个数组,这个数组的长度就是网格中列的数目,并且这个数组也包含了给定行中的数据
4、杂乱的网格和多维数组
杂乱的网格有固定数目的行,但每一行中的列数是不同的。
一个三维数组,可以给定它深度、高度、宽度,使用3个整数作为索引来访问每一项。
四、链表结构
1、单链表结构和双链表结构
使用链表结构必须从结构的一段开始沿着链表进行,直到达到想要的位置
[1]、一旦找到一个插入点或删除点,就可以进行插入和删除,不需要在内存中移动数据项
[2]、在每一次插入和删除的过程中,链表结构会调整大小,并且不需要额外的内存代价,也不需要复制数据项
2、非连续性内存和节点
非连续性内存:链表结构中的项的逻辑顺序和内存中的顺序解耦了,也就是说,只要计算机遵照链表结构中一个给定项的地址和位置的链接,就能在内存中找到它的单元在何处。
节点:链表结构中的基本单位,包含一个数据项和到结构中的下一个节点的链接。
python使用对对象的引用建立起了节点和链表结构,通过定义包含两个字段的对象,从而定义一个单链表节点,这两个字段是:①数据项的一个引用②到另一个节点的一个引用
3、定义一个单链表节点类
class Node(object):
def __init__(self,data,next=None):
self.data=data
self.next=next
4、使用单链表节点类
'''
用循环创建一个链表结构
'''
head=None
for count in range(1,6):
head=Node(count,head)
while head != None:
print(head.data)
head=head.next
输出5,4,3,2,1,节点在打印后依次被垃圾回收了
五、单链表结构上的操作
通过操作结构中的链接来模拟基于索引的操作。
1、遍历
使用临时指针变量,节点没有被删除
'''
遍历
'''
head=None
for count in range(1,6):
head=Node(count,head)
probe=head
while probe != None:
probe=probe.next
2、搜索
'''
搜索
'''
tagetItem='要查找的数据'
head=None
for count in range(1,6):
head=Node(count,head)
probe=head
while probe != None and tagetItem != probe.data:
probe=probe.next
if probe != None:
'tagetItem has been found'
pass
else:
'tagetItem is not in the linked structure'
pass
和数组不同,链表结构并不支持随机访问
3、替换
'''
替换
'''
tagetItem='要替换的数据'
newItem='替换的数据的新内容'
head=None
for count in range(1,6):
head=Node(count,head)
probe=head
while probe != None and tagetItem != probe.data:
probe=probe.next
if probe != None:
probe.data=newItem
return True
else:
'没有找到要替换的项'
return False
如果目标不存在,就不会发生替换
4、在开始出插入
head=Node(newItem,head)
head为None时,向结构中插入了第一项,head不为None时,将第二项插入到了同一结构的开头处
head不为None时,并不需要复制数据并向下移动数据,也不需要额外的内存。
5、在末尾插入
head指针为None时,将head指针设置为新的节点,不为None时,搜索最后一个节点,并将其next指针指向新的节点
'''
在末尾擦插入
'''
newItem='要插入的数据'
newNode=Node(newItem)
head=None
for count in range(1,6):
head=Node(count,head)
if head is None:
head=newNode
else:
probe=head
while probe.next != None:
probe=probe.next
probe.next=newNode
6、从开始处删除
'''
从开始处删除
'''
removedItem=head.data
head=head.next
return removedItem
7、从末尾删除
只有一个节点,head指针设置为None;
大于一个节点,搜索到倒数第二个节点并将其next指针设置为None
'''
从末尾删除
'''
removedItem=head.data
if head.next is not None:
head=None
else:
probe=head
while probe.next.next != None:
probe=probe.next
removedItem=probe.next.data
probe.next=None
return removedItem
8、在任何位置插入
该节点的next指针为None,应将新的项放在链表结构的末尾。
该节点的next指针不为None时,必须将新的项放在位于i-1和i的节点之间
'''
在任意位置插入
'''
if head is None or index <= 0:
head=Node(newItem,head)
else:
probe=head
while index > 1 and probe.next != None:
probe=probe.next
index-=1
probe.next=Node(newItem,probe.next)
9、从任意位置删除
'''
在任意位置删除
'''
if head is None or index <= 0:
removedItem=head.data
head=head.next
return removedItem
else:
probe=head
while index > 1 and probe.next.next != None:
probe=probe.next
index-=1
removedItem=probe.next.data
probe.next=probe.next.next
return removedItem
10、复杂度权衡:时间、空间和单链表结构
单链表结构相对于数组的主要优点并不是时间性能,而是内存性能,链表结构额外的内存在于为指针提供n个内存单元格
六、链表的变体
1、带有一个哑头节点的循环链表结构
head=Node(None,None)
head.next=head
probe=head
while index > 0 and probe.next !=head:
probe=probe.next
index-=1
probe.next=Node(newItem,probe.next)
2、双链表结构
class Node(object):
def __init__(self,data,next=None):
self.data=data
self.next=next
class TwoWayNode(Node):
def __init__(self,data,previous=None,next=None):
Node.__init__(self,data,next)
self.previous=previous
head=TwoWayNode(1)
tail=head
for data in range(2,6):
tail.next=TwoWayNode(data,tail)
tail=tail.next
probe=tail
while probe != None:
print(probe.data)
probe=probe.previous
本文参考文献:[1]Kenneth A.Lambert著;李军译.数据结构:Python语言描述[M].北京:人民邮电出版社,2017:69-95.