链表---Singly Linked List
提到链表一般指的是单链表,这种链表由节点组成,节点包括存放数据的数据域和指向下一个节点的指针域。这样的链表有两个特点:
- 头指针head永远指向第一个节点(头指针本身不是节点)
- 最后一个节点的指针永远指向空
因此,首先需要定义节点类,该类包括两个数据成员,即数据data
和指向下一个节点的指针pt
。在创建一个新节点时,其数据应赋值为空,指针应指向空,因此在初始化函数传入了默认值。
class Node:
def __init__(self, data=None, pt=None):
self.data = data
self.pt = pt
然后,建立链表类及其相关的操作,第一个问题就是链表该如何初始化,也就是一个不包含任何节点的空链表应该是怎样的?
答:空链表只需要一个头指针,将该指针指向空;为了方便创建length
变量记录链表的长度,初始值为0。
class LinkedList:
# 链表初始化
def __init__(self):
self.length = 0
self.head = None
# 返回链表长度的方法
def list_length(self):
return self.length
链表的插入
在链表的头部插入
“巧妇难为无米之炊”,不管在哪插首先得有节点;然后才是把节点链接入链表。因此头插可以分三步进行:
- 创建新节点,并将待插入的数据存入新节点
- 将新节点的指针指向头结点
- 将头指针指向新节点
注意:步骤2和步骤3不可以调换顺序,头指针存着头节点的地址,如果先将头指针指向了新节点,相当于头节点就“失联”了,就没办法将新节点链接到头结点了。
# Insert at beginning
def insert_at_begin(self, data):
new_node = Node(data, ) # step1
new_node.pt = self.head # step2
self.head = new_node # step3
self.length += 1
显然,当链表为空,该方法仍然有效,其时间复杂度为O(1)。
在链表的中间插入
第一步仍然是创建新节点并存入数据,假设考虑在插入位置pos
后面进行插入,则必须先找到指向插入位置节点的指针,然后将新节点和插入位置节点的下一个节点链接起来,再把插入位置节点和新节点链接起来。因此,中间插应分四部进行:
- 创建新节点,并将待插入的数据存入新节点
- 找到指向插入位置节点的指针
- 新节点的指针指向插入位置节点的下一个节点
- 插入位置节点指针指向新节点
# Insert at middle
def insert_at_mid(self, data, pos):
new_node = Node(data, ) # step1
current = self.head # step2
for i in range(pos-1):
current = current.pt
new_node.pt = current.pt # step3
current.pt = new_node # step4
- 为什么循环
pos-1
次?因为从第一个节点到第pos个节点向前移动pos-1次 - 是空链表能运行吗?不能,因为此时循环将不执行,current指向空,不存在current.pt
- 时间复杂度多少?O(n)
为了使得方法在空表时也能执行,进一步修改:
# Insert at middle
def insert_at_mid(self, data, pos):
if pos < 0 or pos > self.length:
print("Error: please input another pos")
elif pos == 0:
self.insert_at_begin(data)
else:
new_node = Node(data, ) # step1
current = self.head # step2
for i in range(pos-1):
current = current.pt
new_node.pt = current.pt # step3
current.pt = new_node # step4
self.length += 1
在链表的尾部插入
第一步仍然是创建新节点并存入数据,然后是找到指向尾节点的指针,尾节点的指针指向新节点。因此,尾插分三步进行:
- 创建新节点,并将待插入的数据存入新节点
- 找到指向尾节点的指针
- 尾节点的指针指向新节点
注意:新节点默认指针指向空
# Insert at the end
def insert_at_end(self, data):
new_node = Node(data, ) # step1
current = self.head # step2
for i in range(self.length-1):
current = current.pt
current.pt = new_node # step3
self.length += 1
同样地,时间复杂度为O(n)。当链表为空时,方法失效,因此进一步改进:
# Insert at the end
def insert_at_end(self, data):
if self.length == 0:
self.insert_at_begin(data)
else:
new_node = Node(data, ) # step1
current = self.head # step2
for i in range(self.length-1):
current = current.pt
current.pt = new_node # step3
self.length += 1
链表的删除
删除操作和插入操作本质上是类似的,因此不再考虑特殊情况而只考虑一般情况,重点掌握逻辑。
在链表头部删除
python由于不需要自己手动释放内存,因此步骤相对其它语言相对简单。
其它语言的流程:
- 新建temp变量指向头指针指向的节点
- 头指针指向下一个节点
delete
temp释放内存
Python流程:
- 头指针指向下一个节点
实现
def delete_from_begin(self):
self.head = self.head.pt # step1
self.length -= 1
在链表中间删除
其它语言的流程:
- 找到待删除节点的前一个节点的指针
- 新建temp变量存储待删除节点的指针
- 将待删除节点的前一个节点的指针指向待删除节点的下一个节点
- 释放待删除节点的内存
Python流程:
- 找到待删除节点的前一个节点的指针
- 将待删除节点的前一个节点的指针指向待删除节点的下一个节点
实现
def delete_from_mid(self, pos):
current = self.head # step1
for i in range(pos-2):
current = current.pt
current.pt = current.pt.pt # step2
self.length -= 1
在链表尾部删除
其它语言流程:
- 找到指向待删除节点的前一个节点的指针
- 新建temp变量存储指向待删除节点的指针
- 将待删除节点的前一个节点指针指向空
- 释放待删除节点的内存
Python流程:
- 找到指向待删除节点的前一个节点的指针
- 将待删除节点的前一个节点指向空
实现
def delete_from_end(self):
current = self.head # step1
for i in range(self.length-2):
current = current.pt
current.pt = None # step2
self.length -= 1
链表的清空
同样,因为python可以自动收集内存,因此链表的清空极为简单,流程如下:
- 头指针指向空
实现
def clear(self):
self.head = None # step1
self.length = 0
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!