关于线性表
定义:线性表的定义是描述其逻辑结构,而通常会在线性表上进行的查找、插入、删除等操作。
线性表作为一种基本的数据结构类型,在计算机存储器中的存储一般有两种形式,一种是顺序存储,一种是链式存储。
线性表的顺序存储
定义
若将线性表L=(a0,a1, ……,an-1)中的各元素依次存储于计算机一片连续的存储空间,这种机制表示为线性表的顺序存储结构。
特点
逻辑上相邻的元素 ai, ai+1,其存储位置也是相邻的;
存储密度高,方便对数据的遍历查找。
对表的插入和删除等运算的效率较差。
程序实现
在Python中,list存放于一片单一连续的内存块,故可借助于列表类型来描述线性表的顺序存储结构,而且列表本身就提供了丰富的接口满足这种数据结构的运算。
顺序存储结构的线性表就是我们常说的列表,列表中附带的功能十分强大。我们通过简单的代码对列表的增删改查进行演示。
#创建一个列表
L = [1,2,5,9,7,8,6]
L.append(10) #增加列表的元素
# 结果[1,2,5,9,7,8,6,10]
L.remove(6) #删除列表元素
# 结果[1,2,5,9,7,8,10]
L[3] = 11 #修改列表元素
# 结果[1,2,5,11,7,8,10]
L.index(3) #查找
#11
L.insert(2,9) #插入
# 结果[1,2,5,9,11,7,8,10]
线性表的链式存储
定义
将线性表L=(a0,a1,……,an-1)中各元素分布在存储器的不同存储块,称为结点,每个结点(尾节点除外)中都持有一个指向下一个节点的引用,这样所得到的存储结构为链表结构。
链表结构
特点
逻辑上相邻的元素 ai, ai+1,其存储位置也不一定相邻;
存储稀疏,不必开辟整块存储空间。
对表的插入和删除等运算的效率较高。
逻辑结构复杂,不利于遍历。
程序实现
我们先创建节点类,用简单的代码对单链表进行模拟。
# 创建节点类
class Node:
"""
自定义类视为节点类,属性作为数据内容
写一个next属性,用来和下一个节点创建联系
"""
def __init__(self, val, next=None):
"""
:param val:必要数据
:param next:下个节点的引用
"""
self.val = val
self.next = next
创建节点并进行实例化
Abby = Node((1,'Abby','w'))
Emma = Node((2,'Emma','w'))
Alex = Node((3,'Alex','m'))
Abby.next = Emma
Emma.next = Alex
不难发现,Abby–>Emma–>Alex,就这样,我们完成了一个简单链表的创建。 那下面我们如果想将一个顺序储存的列表变成一个链表,那又需要如何实现呢?
首先,我们需要对目标列表进行链表初始化。
观察以下代码:
class LinkList:
"""
生成单链表,通过实例化的对象代表一个链表
可以调用具体的操作完成各种功能
"""
def __init__(self):
self.head = Node(None) # 表头
# 初始化链表
def init_list(self, list_):
p = self.head
for i in list_:
p.next = Node(i)
p = p.next
我们在初始化链表之前,需要明确链表所在的位置,以方便我们去查找。所以,我们在这先建立一个表头。
在我们初始化链表时,我们需要遍历列表,让列表中每个元素都建立对应节点并与下一元素相连。我们创建出一个类似于指针变量的变量p,在遍历时,元素与元素之间建立节点。p存储的是表头地址,创建节点,储存元素,表头的next指向第一个元素,第一个元素的next又通过p去与下一个元素建立节点……完成初始化。
完成链表的初始化后,我们可以定义对链表的相关操作。
# 遍历链表
def show(self):
p = self.head.next
while p:
print(p.val)
p = p.next
# 清空链表
def clear(self):
self.head.next = None
# 尾部插入
def append(self, value):
p = self.head
while p.next:
p = p.next
p.next = Node(value)
我们实现链表的清空,这里利用的是python它的一个内存处理机制,数据没有被变量绑定,就会自动释放。所以,我们只要实现表头的next为空值就可以实现这样的功能。
当我们需要在链表的尾部插入数据时,我们需要将整个链表遍历一遍才能实现插入数据的功能,此处就体现了链表不便于遍历操作的缺点。
# 头部插入
def head_insert(self, value):
node = Node(value)
node.next = self.head.next
self.head.next = node
# 指定位置插入
def insert(self, index, value):
p = self.head.next
for i in range(index):
if p.next is None:
break
p = p.next
# 插入节点
node = Node(value)
node.next = p.next
p.next = node
头部插入与尾部插入不同,我们只需要找到表头,就可以进行插入操作。先创建节点,让新节点中的next与表头元素的next建立联系,再让表头的next与新节点建立联系,完成插入。
同理,指定位置插入则需要在制定位置的前一个元素插入节点,重新建立联系,完成插入。
# 删除节点
def remove(self, value):
p = self.head
# 移动至目标节点的上一个节点
while p.next.val != value and p.next is not None:
p = p.next
if p.next is None:
raise ValueError("x is not in list")
else:
p.next = p.next.next
# 获取某个节点的值
def search(self, index):
if index < 0:
raise IndexError("Index out of range")
# 移动p
p = self.head.next
for i in range(index):
if p is None:
raise IndexError("Index out of range")
p = p.next
return p.val
删除节点的时候我们需要注意的是,寻找的值在不在链表中,如果找到,只需要斩断该元素与相邻两元素之间的联系就可以了。
而在我们需要查找某个节点的值的时候,我们需要规范查找的操作,index大于0。移动指针p,当p所联系的值是None时,可以确定输入的地址大于链表的长度。