线性表
定义:
具有相同特性的数据元素的一个有限序列。
特征:
①所有数据元素的类型相同
②线性表是有限个数据元素构成的
③线性表中的数据元素与位置相关,即每个数据元素有惟一的序号(或索引)
线性表中的每个元素最多只有一个前驱元素和一个后继元素。
顺序存储
把线性表中的所有元素按照其逻辑顺序依次存储到存储器中一块连续的存储空间。线性表的顺序存储结构称为顺序表。
# 在顺序表中插入e作为第i个元素
def Insert(self, i, e):
assert 0<=i<=self.size # 检测参数i正确性的断言
if self.size == self.capacity: # 满时倍增容量
self.resize(2*self.size)
for j in range(self.size, i, -1): # 将data[i]及后面的元素后移一个位置
self.data[j] = self.data[j-1]
self.data[i] = e # 插入元素e
self.size += 1 # 长度增1
# 删除表内第i个元素
def Delete(self, i):
assert 0<=i<=self.size-1 # 检测参数i正确性的断言,注意这里为self.size-1与后面j的取值有关
for j in range(i, self.size-1): # 遍历i到size-1,依次赋值给j
self.data[j] = self.data[j+1] # 将data中元素的位置前移一位
self.size -= 1 # 元素个数减一,长度减一
# 若当前容量大于初始容量并且实际长度仅为当前容量的1/4(称为缩容条件),则将当前容量减半。
# 之所以为1/4,是因为预留一部分容量,以免有新内容进入又要扩容。
if self.capacity>self.initcapacity and self.size<=self.capacity/4:
self.resize(self.capacity//2) # 减半
链式存储
线性表的链式存储结构称为链表。
在链表中每个结点不仅包含元素本身的信息(称为属性),而且包含元素之间逻辑关系的信息,即一个结点中包含后继结点的地址信息或者前驱结点的地址信息,称为指针属性,这样将可以通过一个结点的指针属性方便的找到后继结点或者前驱结点。
单链表
如果每个结点只设置一个指向其后继结点的指针属性,这样的链表称为线性单向链接表,简称单链表。
class LinkNode: # 单链表节点类
def __init__(self, data=None): # 构造方法
self.data = data # data属性,存储元素的数据成员
self.next = None # next属性为空
class LinkList: # 单链表类
def __init__(self): # 构造方法
self.head = LinkNode() # 头结点head,实际上为None,以self.head实例化LinkNode()
self.head.next = None # 尾结点设置为None
# 头插法
# 从一个空表开始,依次读取数组a中的元素。
# 生成新结点s,将读取的数据存放到新结点的数据成员中。
# 将新结点s插入到当前链表的表头上。
# 优点:头节点总是已知的;缺点:顺序相反
def CreateListF(self,a): # 由数组a整体建立单链表
for i in range(0, len(a)): # 遍历0到a的长度,依次赋值给i
s = LinkNode(a[i]) # 新建存放a[i]元素的结点s。以s实例化LinkNode,其self.data为a[i]
s.next = self.head.next # 将s结点插入到开始结点之前,头结点之后
self.head.next = s
# 尾插法
# 前两条同上
# 将新结点s插入到当前链表的表尾上。
# 优点:和数组a顺序一致
def CreateListR(self, a):
t = self.head # t始终指向尾结点,开始时指向头结点
for i in range(0, len(a)): # 遍历
s = LinkNode(a[i]) # 新建存放a[i]元素的结点s。以s实例化LinkNode,其self.data为a[i]
t.next = s # s结点插入t结点之后
t = s # t结点除了开始时指向头结点,后面都指向尾结点
t.next = None # 将尾结点的next设置为空
# 查找链表中序号为i的结点
def geti(self, i):
p = self.head # p指向头结点
j = -1
while(j<i and p is not None): # 遍历查找
j += 1
p = p.next
return p
# 添加元素到链表末尾
def Add(self, e):
s = LinkNode(e) # 新建结点s
p = self.head # p指向头结点
while p.next is not None: # 查找尾结点
p = p.next
p.next = s # 在尾结点后插入结点s
# 求单链表的长度
def getsize(self):
p = self.head # p指向头结点
cnt = 0
while p.next is not None: # 遍历,找到尾结点为止
cnt += 1
p = p.next
return cnt
# 求链表中序号为i的元素值
def __getitem__(self, i):
assert i>=0
p = self.geti(i)
assert p is not None
return p.data
# 设置链表中序号为i的元素值
def __setitem__(self, i, e):
assert i>=0
p = self.geti(i)
assert p is not None
p.data = e
# 获取链表中第一个值为e的元素的序号
def GetNo(self, e):
j = 0
p = self.head.next
while p is not None and p.data != e: # 查找元素e
j += 1
p = p.next
if p is None:
return -1
else:
return j
# 插入e作为第i个元素
def Insert(self, i, e):
assert i>=0
s = LinkNode(e)
p = self.geti(i-1)
assert p is not None
s.next = p.next
p.next = s
# 删除
def Delete(self, i):
assert i>=0
p = self.geti(i-1)
assert p!=None and p.next is not None
p.next = p.next.next
# 输出
def display(self):
p = self.head.next
while p is not None:
print(p.data, end=' ')
p = p.next
print()
if __name__=='__main__':
L = LinkList()
print()
print("建立空链表L")
a = [1,2,3,4,5,6,7]
print("1-7创建L")
L.CreateListR(a)
print("L[长度=%d]:" %(L.getsize()), end=''),L.display()
print("插入7-12")
for i in range(7, 12):
L.Add(i)
print("L[长度=%d]:" %(L.getsize()), end=''),L.display()
print("序号为2的元素=%d" %(L[2]))
print("设置序号为2的元素为20")
L[2]=20
print("L[长度=%d]:" %(L.getsize()), end=''),L.display()
x = 6
print("第一个值为%d的元素序号=%d" %(x, L.GetNo(x)))
n = L.getsize()
for i in range(n-2):
print("删除首元素")
L.Delete(0)
print("L[长度=%d] :" %(L.getsize()), end=''),L.display()
双链表
如果每个结点中设置两个指针属性,分别用于指向其前驱结点和后继结点,这样的链表称为线性双向链接表,简称双链表。
无前驱结点或者后继结点的相应指针属性用常量None表示。
顺序表和链表的比较
基于空间的考虑
存储密度越大,存储空间的利用率就越高。
仅从存储密度看,顺序表的存储空间利用率高。
当线性表的长度变化不大,易于事先确定时,为节省存储空间,宜采用顺序表作为存储结构。
反之,采用链表。
基于时间的考虑
顺序表具有随机存取特性,给定序号查找对应的元素值的时间为O(1)
链表不具有随机存取特质,只能顺序访问,查找对应时间为O(n)
若线性表的运算只要是查找,很少插入、删除,宜采用顺序表。
若频繁的插入、删除,宜采用链表。