单向链表
文章目录
链表结构
链表是一种物理存储单元上非连续、非顺序的存储结构
数据元素的逻辑顺序通过链表中的指针链接次序实现
链表由一系列结点组成,结点可以在运行时动态生成
每个结点包括两个部分:存储数据元素的数据域、存储下一个节点地址的指针域
用python实现单向链表
用class分别定义结点(Node) 和 链表(linklist) 两个类,添加想要的属性即可!
代码实现如下:
class Node:
# 结点类
def __init__(self,data,_next = None):
self.data = data
self._next = _next
代码实现如下:
class Singlelinklist:
def __init__(self):
self.head = None
self._length = 0
单项链表所具有的操作
方法 | 功能 |
---|---|
is_empty() | 链表是否为空 |
length() | 返回链表的长度 |
node_list() | 返回链表中所有节点的值组成的列表 |
add(data) | 链表的头部添加一个节点,值为data |
append() | 链表尾部添加节点,值为data |
insert(pos,data) | 指定的位置添加节点,值为data |
remove(data) | 删除第一个值为data的节点 |
modify(pos,data) | 修改指定位置元素的值为data |
search(data) | 查找节点是否存在 |
往链表头部添加一个结点add()
实现步骤: 链表为空的时候照样成立
- 创建一个新的节点
- 将新的节点的next属性指向原来的头部节点,即:self.head
- 将头部节点重新指向创建的新节点
- 链表的长度属性加1
链表为空的情况如下:
代码实现如下:
注意本文def的所有的方法(函数)都需要放到类(class)中才可实现,其是一个类方法
def add(self,data): # 请将我放入类中执行
node = Node(data)
node._next = self.head
self.head = node # 存储的是一个类对象 而不是对象 类对象中包括对象和下一个类对象的地址
self.lenth += 1
返回链表中所有节点的值组成的列表
方法1实现步骤
循环节点法
- 创建一个新的列表
- 将每个节点的data属性添加到列表中
- 返回列表
具体第二步入何进行实现?如何才能遍历每个节点,请看下图:
代码实现如下:
def nodes_list(self):
res = []
cur = self.head # 第一个结点
while cur: #如果有节点进入循环
res.append(cur.data) #将该节点的数据添加到列表中
cur = cur.next # 并赋值下一节点 如果下一节点为 None 跳出循环返回res
return res
方法2实现步骤
循环索引法
- 创建一个列表res
- 循环遍历指定的次数
- 将指定的data值添加到列表中
- 返回res列表
具体实现请看下图:
具体的如何精确控制循环的次数?
a= 4 # a是多少就循环多少次
while a:
print(f'这是第{5-a}次循环')
a -= 1
这是第1次循环
这是第2次循环
这是第3次循环
这是第4次循环
代码实现如下:
def nodes_list(self):
res = []
a = self.length() #循环self.length()次
cur = self.head
while a:
res.append(cur.data)
cur = cur.next
a -= 1
return res
往链表的末尾添加一个结点
实现方法1步骤
- 循环找到尾节点
- 将尾节点的next属性指向新的节点
- 链表的长度加1
- 通过next属性是否为None来指向尾部节点详见博主原创文章 ==双向链表python实现==(附件1:双向链表寻找尾结点(同单向))内容
代码实现如下:
def append(self, data):
node = Node(data)
cur = self.head
if cur: # 如果head属性有节点,就证明不是空链表
while cur.next: # 当cur为尾节点的时候 跳出循环
cur = cur.next
cur.next = node
else:
self.head = node
self._length += 1
实现方法2(通过索引找到指定的节点)
- 通过索引找到指定的节点的方法详见博主原创文章 ==双向链表python实现==(附件2:通过索引寻找指定的结点)内容
def append_node(self,data):
node = Node(data)
if self.is_empty():
self.head = node # 空链表的执行条件
cur = self.head # 非空链表的执行条件
a = self._length - 1
while a:
cur = cur.next
a -= 1
cur.next = node
self._length += 1
在指定的位置插入结点
示意图如下:
思考这么一个问题:
就拿上面盖的这个图来说,给索引为2的地方插入一个值,重要的是找索引为1的节点还是索引为2的节点?
显然索引为1的节点才是重要的!!!
- 首先Step1是将新的节点的next属性指向原来索引为2的节点,也就是索引为1的节点的next属性(就是索引为2的节点)
- Step2是将索引为1的next属性指向新的节点,如果得到索引为2的节点无法直接完成操作(需要定义一个前驱节点)
实现具体步骤
代码实现如下:
- 创建新的节点node
- 找到索引为pos-1的节点(前面好像说了如何通过索引找到指定的节点呦)
- 将pos-1结点 next属性(原来pos位置上的结点)赋值给新的结点的next属性
- 将新的结点赋值给 pos-1结点的next属性
- 链表长度加1
def insert(self,pos,data): # pos代表插入节点的位置,也就是 第pos+1 个结点 pos是索引
'''
非正常情况下
如果小于0 默认添加到头部
如果超出范围 默认添加到尾部
'''
if pos <= 0:
self.head = node
elif pos > self.lenth: # 在此处pos > self.lenth-1也行 只不过 当添加末尾元素的时候 会调用 append()方法
self.append(data)
else:
node = Node(data)
cur = self.head
pos -= 1
while pos: # 这里应用的很巧妙 因为没有像传统的循环那样创建一个计数器 直接用现有的pos
cur = cur.next
pos -= 1
node.next = cur.next
cur.next = node
self.lenth += 1
删除第一个数据为data的元素
具体步骤
- 创建前驱节点prev
- 遍历链表,找到值为data的结点
- 将被删除结点的next赋值给被删除结点的前一节点(也就是说程序执行的过程中,为前后两个结点共同执行)
- 如果data找不到返回-1 如果成功删除直接跳循环 返回0
- 链表长度-1
'''仅仅只是Singlelinklist类方法定义的部分 其他的部分参考上面的'''
def remove(self,data):
# 删除链表中第一个值为data的元素
cur = self.head
prev = None # 创建一个前驱节点
while cur:
if cur.data == data: # 进行删除操作
# 内层的if 都是删除操作
if not prev: # 删除的节点是第一个节点
self.head = cur.next
'''
原本的self.head 为 cur 但是如果是第一个的要被删除的话 那么 self.head 必须指向第二个 即第一个的next属性
'''
else:
prev.next = cur.next
'''
如果删除的结点不是第一个结点 那么就将被删除结点的next属性赋值给 被删除结点的前一个结点prev
'''
self.lenth -= 1
return 0 # 加上这行代码 只删除一个 不加的话 删除全部
prev = cur
cur = cur.next
return -1
在这里再提供另一种代码的实现方法
但是下面的代码的可变形性不太好,自行使用哈
def remove_(self,data):
cur = self.head
if cur.data == data:
self.head = self.head.next
self._length -= 1
return 0
prev = None
while cur:
if cur.data == data:
prev.next = cur.next
self._length -=1
return 0
else:
prev = cur
cur = cur.next
return -1
修改链表中的某一结点的data
- 正常情况:
只用self.head一次 然后 进行pos次循环next 就可以找到相对应的结点
将节点的data属性进行修改即可 - 非正常情况下 输入的 值超出范围的时候
代码实现如下(这部分又涉及到了 通过索引找到指定的节点=附件2)
def modify(self,pos,data):
'''
修改链表中指定位置的值
'''
if 0 <= pos < self.lenth:
cur = self.head
while pos:
cur = cur.next
pos -= 1
cur.data = data
else:
print('索引不在范围内')
查找链表中是否值为data的结点
def search(self,data):
# 查找链表中是否有值为data的结点
'''
循环遍历每一个结点 判断其值是否为data
如果是 返回索引值 否则 返回None
'''
cur = self.head
count = 0
while cur:
if cur.data == data:
return f'找到该数据了,其索引为{count}'
else:
cur = cur.next
count += 1
return '未找到data的结点'
另一种实现方法(方法本身不太好,但是是想引出一个知识点):
def search(self,data):
'''
查找结点是否存在
'''
cur = self.head
while cur != None and cur.data != data :
cur = cur.next
if cur == None:
print('不存在')
elif cur.data == data:
print('存在')
本行代码
while cur != None and cur.data != data :😗
涉及到一个知识点,看代码
如果为空链表cur.data != data会报错,但是前面有一个cur != None值为False就已经可以确定条件表达式的值为False,后面的代码解释器直接跳过不会执行,也就不会报错。
链表和顺序表的时间复杂度
操作 | 链表 | 顺序表 |
---|---|---|
访问元素 | O(n) | O(1) |
头部插入/删除 | O(1) | O(n) |
尾部插入/删除 | O(n) | O(1) |
中甲插入/删除 | O(n) | O(n) |
插入操作多的话,用链表
查找操作多的话,用顺序表
附件:用python实现链表功能的全部代码
# @Author :泰敢人辣
# Date :2023-07-21 12:40
class Node:
# 结点类
def __init__(self,data,_next=None):
self.data = data
self.next = _next
class Singlelinklist:
def __init__(self):
self.head = None # 链表的头节点
self.lenth = 0 # 链表的长度
def is_empty(self):
return self.lenth == 0
def _lenth(self):
return self.lenth
def nodes_list(self):
res = []
a = self.head # 第一个结点
while a: #如果右节点进入循环
res.append(a.data) #将该节点的数据添加到列表中
a = a.next # 并赋值下一节点 如果下一节点为 None 跳出循环返回res
return res
def add(self,data):
#往链表头部添加一个结点,值为data
'''
创建一个新的结点data
使其node指向当前链表的头节点
并将head指向当前链表的node结点
'''
node = Node(data) # 创建类对象
# print(f'1的类对象的id为{id(node)}')
node.next = self.head
self.head = node # 存储的是一个类对象 而不是对象 类对象中包括对象和下一个类对象的地址
self.lenth += 1
def append(self,data):
node = Node(data) # 创建新的类对象
'''
找到链表的尾部结点 特征为 其next类属性为None
将其.next属性改为类对象node
'''
a = self.head # 第一个结点
if a: # 如果链表有头节点 就证明他不是一个空链表
while a.next: # a.next代表的是下一个节点 如果有东西 进入循环 反之 下一个结点就是新创建的结点
a = a.next # 有东西 接着往下走 知道找到没有东西的那一个 赋值node
a.next = node
else:
self.head = node
self.lenth += 1
def insert(self,pos,data): # pos代表插入节点的位置,也就是 第pos+1 个结点 pos是索引
'''
非正常情况下
如果小于0 默认添加到头部
如果超出范围 默认添加到尾部
'''
node = Node(data)
if pos <= 0:
self.head = node
elif pos > self.lenth: # 在此处pos > self.lenth-1也行 只不过 当添加末尾元素的时候 会调用 append()方法
self.append(data)
print('append()方法被调用执行了')
else:
'''
正常情况下
第一步:创建新的node对象 ===========
第二步:找到索引为pos-1(前)结点
第三步:将pos-1结点 next属性(原来pos位置上的结点)赋值给新的结点的next属性
第四步:将新的结点赋值给 pos-1结点的next属性
第五步:长度加1
'''
a = self.head
pos -= 1
while pos : # 这里应用的很巧妙 因为没有像传统的循环那样创建一个计数器 直接用现有的pos
a = a.next
pos -= 1
node.next = a.next
a.next = node
self.lenth += 1
def remove(self,data):
# 删除链表中第一个值为data的元素
'''
1. 遍历链表,找到值为data的结点
2. 将被删除结点的next赋值给被删除结点的前一节点(也就是说程序执行的过程中,为前后两个结点共同执行)
3. 如果data找不到返回-1 如果成功删除直接跳循环 返回0
4. 链表长度-1
:param data:
:return:
'''
cur = self.head
prev = None
while cur:
if cur.data == data:
# 内层的if 都是删除操作
if not prev:
self.head = cur.next
'''
原本的self.head 为 cur 但是如果是第一个的要被删除的话 那么
self.head 必须指向第二个 即第一个的next属性
'''
else:
prev.next = cur.next
'''
如果删除的结点不是第一个结点 那么就将被删除结点的next属性赋值给
被删除结点的前一个结点prev
'''
self.lenth -= 1
return 0 # 加上这行代码 只删除一个 不加的话 删除全部
prev = cur
cur = cur.next
return -1
def modify(self,pos,data):
'''
修改链表中指定位置的值
思路
正常情况下
只用self.head一次 然后 进行pos次循环next 就可以找到相对应的结点
将节点的data属性进行修改即可
非正常情况下 输入的 值超出范围的时候
:param pos:
:param data:
:return:
'''
if 0 <= pos < self.lenth:
cur = self.head
while pos:
cur = cur.next
pos -= 1
cur.data = data
else:
print('索引不在范围内')
def search(self,data):
# 查找链表中是否有值为data的结点
'''
循环遍历每一个结点 判断其值是否为data
如果是 返回索引值 否则 返回None
:param data:
:return:
'''
cur = self.head
count = 0
while cur:
if cur.data == data:
return f'找到该数据了,其索引为{count}'
else:
cur = cur.next
count += 1
return '未找到data的结点'
if __name__ == '__main__':
l1 = Singlelinklist()
l1.is_empty()
print(l1.nodes_list())
l1.append(2)
print(l1.nodes_list())
l1.append(3)
print(l1.nodes_list())
l1.append(4)
print(l1.nodes_list())
l1.append(5)
print(l1.nodes_list())
l1.insert(1,100)
print(l1.nodes_list())
l1.insert(4,100)
print(l1.nodes_list())
l1.insert(7,100)
print(l1.nodes_list())
print('==================链表元素data的删除操作=======================')
l1.remove(100)
print('删除100')
print(l1.nodes_list())
l1.remove(2)
print('删除2')
print(l1.nodes_list())
print('==================链表元素data的修改操作=======================')
l1.modify(0,10000)
print('修改0为10000')
print(l1.nodes_list())
l1.modify(3, 20000)
print('修改3为20000')
print(l1.nodes_list())
print('==================链表元素data的查找操作=======================')
print(l1.search(3))
print('==================判断链表是否为空=======================')
print(l1.is_empty())
附件2:单向链表寻找尾结点
cur = self.head
while cur.next: # 加上next尾节点不会进入循环 也就会得到尾节点
cur = cur.next
cur = self.head
while cur: #不加next尾节点进入循环,最终的结果只会是None
cur = cur.next