(二)线性表
线性表
线性结构的基本特点:除第一个元素无直接前驱,最后一个元素无直接后继,其他元素都有一个前驱和一个后继。理解起来就是:第一个元素不能向前访问,最后一个元素不能向后访问,中间的元素都可以前后访问其他元素。
线性表:由有限个数据特性相同的数据元素构成的***有限序列***
根据线性表的实际存储方式,分为两种实现模型:
- 顺序表:将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示;
- 链表:将元素存放在通过链接构造起来的一系列存储块中。
1.顺序表
内存、类型本质、连续存储
内存:计算机中的内存是一个连续的存储空间,而连续的存储空间当中是由基本的存储单元(以一个字节作为标识,即是一个字节的八位整体作为一个地址标识)组成。
类型:数据的类型决定了计算机要以多大的空间来存储。以及要将数据取出来的时候,怎么恢复到原来的数据。通常一组相同类型的数据会采取顺序表的形式去存储。
(1)顺序表的两种形式:
-
数据元素本身连续存储,每个元素所占的存储单元大小固定相同,元素的下标是其逻辑地址,而元素存储的物理地址(实际内存地址)可以通过存储区的起始地址加上逻辑地址(第i个元素)与存储单元大小(c)的乘积计算而得,即
Loc(ei)=Loc(e0)+c*i
因此,访问指定元素时无需从头遍历,通过计算便可以得到对应地址,其时间复杂度为O(1).
- 如果元素的大小不统一,则需要采取元素外置的形式,将实际数据元素另行存储,而顺序表中各单元位置保存对应元素的地址信息(即链接)。由于每个链接所需的存储量相同,通过上述公式,可以计算出链接的存储位置,而后顺着链接找到实际存储的数据元素。图b这样的顺序表也称为对实际数据的索引,这是最简单的索引结构。
(2)顺序表的结构与实现
①顺序表的结构
一个顺序表的完整信息包括两部分(表头信息和数据区),即一部分是表中的元素集合,另一部分是为了实现正确操作而需要记录的信息,即有关表的整体情况的信息,这部分信息主要包括元素存储区的容量和当前表中已有的元素个数两项。
②顺序表的两种基本实现方式
一体式结构和分离式结构。
一体式结构:存储表信息的单元与元素存储区以连续的方式安排在一块存储区中,两部分数据的整体形成一个完整的顺序表对象。一体式结构整体性强,易于管理,但是由于数据区和存储区域是表对象的一部分,顺序表创建后,元素存储区就固定了;
分离式结构:表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联。
③元素存储区替换
分离式结构若想更换数据区,只需要将信息区中的数据区链接地址更新即可,该顺序表对象不变;而一体式若想更换数据区只能整体搬迁,整个顺序表对象改变了。
④元素存储区扩充
采用分离式结构的顺序表,可以在不改变表对象的前提下对其数据存储区进行扩充,所有使用这个表的地方都不用修改,这种称为动态顺序表,因为其容量可以在使用中动态变化。
- 扩充的两种策略
- 每次扩充增加固定数目的存储位置;特点:节省空间,但是扩充操作频繁;
- 每次扩充容量加倍。特点:减少了扩充操作的执行次数,但是可能会浪费空间,用空间换时间。
(3)顺序表的操作
①增加元素
表尾端加入元素,时间复杂度O(1);非保序加入元素(不常见),时间复杂度O(1);保序加入元素,时间复杂度O(n)
②删除元素
删除表尾元素,时间复杂度O(1);非保序删除元素(不常见),时间复杂度O(1);保序删除元素,时间复杂度O(n)
(4)Python中的顺序表
Python中的list和tuple两种类型采用了顺序表的实现技术。
list是一种采用分离式技术实现的动态顺序表。
(5)顺序存储结构的优缺点
-
优点
- 只需存放数据元素自身信息,存储密度大、空间利用率高;
- 元素的位置可以用一个简单、直观的解释式表示;
-
缺点
- 存储空间除了需要实现分配之外,还需按最大需要的空间分配存储;
- 基本运算的效率低下。
但是,线性表的顺序存储结构仍然是较广泛使用的一种基本数据结构。
2.链表
链表主要包括单链表、循环链表和双向链表三种形式,最基本的是单链表。
2.1 单向链表
(1)单链表定义
单向链表也称为单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
Python中变量的本质就是链接,比如a=node2,表示变量a指向node2的地址。
(2)单链表及结点的定义代码
class SingleNode(object):
"""结点"""
def_init_(self,elem):
self.elem=elem
sel.next=None
class SingleLinkList(object):
"""单链表"""
def_init_(self,node=None):
self._head=node
def is_empty(self):
"""判断链表是否为空"""
return self.head==None
def length(self):
"""链表长度"""
#cur游标,用来移动遍历节点
cur=self._head
#count 计数,记录数量
count=0
while(cur !=None)
count+=1
cur=cur.next
return count
def travel(self):
"""遍历链表"""
#cur游标,用来移动遍历节点
cur=self._head
while(cur !=None)
print(cur.elem)
cur=cur.next
def add(self,item):
"""链表头部添加元素,头插法"""
node=Node(item)
node.next=self._head
self._head=node
def append(self,item):
"""链表尾部添加元素,尾插法"""
node=Node(item)
if self.is_empty():
self._head=node
cur=self._head
while(cur.next!=None):
cur=cur.next
cur.next=node
def insert(self,pos,item):
"""指定位置添加元素
:param pos 从0开始
"""
if pos<=0:
self.add(item)
elif pos>(self.length()-1):
self.append(item)
else:
pre=self._head
count=0
while(count<pos-1)
count+=1
pre=pre.next
#当循环退出后,pre指向pos-1位置
node=Node(item)
node.next=pre.next
pre.next=node
def remove(self,item):
"""删除节点"""
cur=self._head
pre=None
while cur!=None:
if cur.elem==item:
#先判断子节点是否是头节点
if cur==self._head:
self._head=cur.next
else:
pre.next=cur.next
break
else:
pre=cur
cur=cur.next
def search(self,item):
"""查找节点是否存在"""
cur=self._head
while cur!=None:
if cur.elem==item:
return True
else:
cur=cur.next
return False
(3)链表和顺序表的对比
链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对于存储空间的使用要相对灵活。
2.2单向循环链表
(1)单向循环链表的定义
单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点。
(2)操作与代码实现
class Node(object):
"""结点"""
def_init_(self,elem):
self.elem=elem
sel.next=None
class SingleCycleLinkList(object):
"""单向循环链表"""
def_init_(self,node=None):
self._head=node
if node:
node.next=node
def is_empty(self):
"""判断链表是否为空"""
return self.head==None
def length(self):
"""链表长度"""
if self.is_empty():
return 0;
#cur游标,用来移动遍历节点
cur=self._head
#count 计数,记录数量
count=1
while(cur.next !=self._head)
count+=1
cur=cur.next
return count
def travel(self):
"""遍历链表"""
if self.is_empty():
return
#cur游标,用来移动遍历节点
cur=self._head
while cur.next !=self._head
print(cur.elem)
cur=cur.next
#退出循环后,cur指向尾节点,但是尾节点的元素未打印
print(cur.elem)
def add(self,item):
"""链表头部添加元素,头插法"""
node=Node(item)
if self.is_empty():
self._head=node
node.next=node
return
cur=self._head
while cur.next!=self._head:
cur=cur.next
#退出循环,cur指向尾节点
node.next=self._head
self._head=node
cur.next=node
def append(self,item):
"""链表尾部添加元素,尾插法"""
node=Node(item)
if self.is_empty():
self._head=node
node.next=node
cur=self._head
while cur.next!=self._head:
cur=cur.next
node.next=self._head
cur.next=node
def insert(self,pos,item):
"""指定位置添加元素
:param pos 从0开始
"""
if pos<=0:
self.add(item)
elif pos>(self.length()-1):
self.append(item)
else:
pre=self._head
count=0
while(count<pos-1)
count+=1
pre=pre.next
#当循环退出后,pre指向pos-1位置
node=Node(item)
node.next=pre.next
pre.next=node
def remove(self,item):
"""删除节点"""
if self.is_empty():
return
cur=self._head
pre=None
while cur.next!=self._head:
if cur.elem==item:
#先判断子节点是否是头节点
if cur==self._head:
#头节点的情况,需要找尾节点
rear=self._head #rear是一个游标
while rear.next!=self._head:
rear=rear.next
self._head=cur.next
rear.next=self._head
else:
#中间节点
pre.next=cur.next
return
else:
pre=cur
cur=cur.next
#退出循环,cur指向尾节点
if cur.elem==item:
if cur==self._head:
#链表只有一个节点
self._head=None
else:
pre.next=cur.next
def search(self,item):
"""查找节点是否存在"""
if self.is_empty():
return False
cur=self._head
while cur.next!=self._head:
if cur.elem==item:
return True
else:
cur=cur.next
#退出循环,cur指向尾节点
if cur.elem==item:
return True
return False
2.3双向链表
每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
class Node(object):
"""结点"""
def _init_(self,item):
self.elem=item
self.next=None
self.prev=None
class DoubleLinkList(object):
"""双链表"""
def_init_(self,node=None):
self._head=node
def is_empty(self):
"""判断链表是否为空"""
return self.head is None
def length(self):
"""链表长度"""
#cur游标,用来移动遍历节点
cur=self._head
#count 计数,记录数量
count=0
while(cur !=None)
count+=1
cur=cur.next
return count
def travel(self):
"""遍历链表"""
#cur游标,用来移动遍历节点
cur=self._head
while(cur !=None)
print(cur.elem)
cur=cur.next
def add(self,item):
"""链表头部添加元素,头插法"""
node=Node(item)
node.next=self._head
self._head=node
node.next.prev=node
def append(self,item):
"""链表尾部添加元素,尾插法"""
node=Node(item)
if self.is_empty():
self._head=node
cur=self._head
while(cur.next!=None):
cur=cur.next
cur.next=node
node.prev=cur
def insert(self,pos,item):
"""指定位置添加元素
:param pos 从0开始
"""
if pos<=0:
self.add(item)
elif pos>(self.length()-1):
self.append(item)
else:
cur=self.head
count=0
while count<pos:
count+=1
cur=cur.next
#当循环退出后,cur指向pos位置
node=Node(item)
node.next=cur
node.prev=cur.prev
cur.prev.next=node
cur.prev=node
def remove(self,item):
"""删除节点"""
cur=self._head
while cur!=None:
if cur.elem==item:
#先判断子节点是否是头节点
if cur==self._head:
self._head=cur.next
if cur.next:
#判断链表是否只有一个节点
cur.next.prev=None
else:
cur.prev.next=cur.next
if cur.next:
cur.next.prev=cur.prev
break
else:
cur=cur.next
def search(self,item):
"""查找节点是否存在"""
cur=self._head
while cur!=None:
if cur.elem==item:
return True
else:
cur=cur.next
return False
2.4 链表存储结构的特点
- 存储位置通过指针反映,进行插入、删除运算时,只需修改相关链结点的指针域。
- 优点:方便、省时
- 缺点:存储空间开销大(数据域+指针域)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!