双向链表
文章目录
双向链表简介
双向链表是一种数据结构,它由一系列节点组成,每个节点包含两个指针:一个指向前一个节点,一个指向后一个节点。这使得在双向链表中可以从任意节点开始,通过前后指针访问相邻节点。
优缺点:双向链表相比单向链表
可以更方便地进行前后遍历和删除操作,但也因为多了一个指针而增加了内存消耗。
双向链表的结构
每一个节点新增加了一个prev属性即指向其前一个结点
特殊的:
头节点的前驱节点=None
尾节点的前驱节点=None
框架建立
每一个节点新增加了一个prev属性即指向其前一个结点
class Node():
def __init__(self,data,_next = None,_prev = None):
self.prev = _prev # 新增
self.data = data
self.next = _next
class Doublelinklist():
def __init__(self):
self.head = None
self.lenth = 0
双向链表具体的操作
链表是否为空
class Doublelinklist():
def __init__(self):
self.head = None
self.lenth = 0
def is_empty(self):
return self.lenth == 0
在链表的头部添加结点
def add(self,data):
'''
在链表的头部添加结点
正常情况下
1.创建新的结点
2.将原来的头节点的前驱属性指向新的节点
3.将新节点的next属性指向下一节点
4.头节点指向新的结点
特殊的
当链表为空的时候
只用将head指向结点即可
:param data:
:return:
'''
node = Node(data)
if self.is_empty():
self.head = node
else:
self.head.prev = node # 让链表中原本的头结点的prev指向新建的节点
node.next = self.head
self.head = node
self.lenth += 1
将链表中的所有的节点的值装进列表
两种思路如下1,2
这里的循环遍历可以有两种方法(在此我们两种方法都采用以下)
1.通过链表本身的长度进行遍历
2.通过节点是否有内容进行遍历
def nodes_list_1(self):
'''
将链表中的所有的data添加到列表中
1.遍历循环所有的链表结点
2.将每个节点的data属性添加到列表中
3.返回列表
附加:
这里的循环遍历可以有两种方法(在此我们两种方法都采用以下)
1.通过链表本身的长度进行遍历
2.通过节点是否有内容进行遍历
:return:
'''
res = []
a = self.lenth
cur = self.head
while a:
res.append(cur.data)
cur = cur.next
a -= 1
return res
def nodes_list_2(self):
'''
通过节点是否有内容进行遍历
将链表中的所有的data添加到列表中
1.遍历循环所有的链表结点
2.将每个节点的data属性添加到列表中
3.返回列表
:return:
'''
res = []
cur = self.head
while cur:
res.append(cur.data)
cur = cur.next
return res
尾部添加结点
同样的在此处的循环遍历方式依旧给出两种
两种方法的本质区别就在于寻找尾部结点的循环方法不同
- 一种判断next属性是否为空值
- 根据长度确定稳定的循环次数
def append1(self,data):
# 在链表的尾部添加结点
'''
1.创建新的结点node
2.循环遍历找到最后一个结点
3.将新节点的prev属性指向尾部结点
4.将尾部节点的next属性指向新节点
5.长度加1
附加:注意判断当链表是空链表的时候如何进行操作
创建新的结点
直接将self.head = node即可
:return:
'''
node = Node(data)
cur = self.head
if cur:
while cur.next:
'''
尾结点cur可以进入循环,但是尾结点的next属性不可进入循环,脱离循环的时候保证cur是尾结点,故 循环条件为下:
while cur.next:
'''
cur = cur.next
node.prev = cur
cur.next = node
else:
self.head = node
self.lenth += 1
def append2(self,data):
node = Node(data)
cur = self.head
if cur:
a = self.lenth - 1
while a:
cur = cur.next
a -= 1
node.prev = cur
cur.next = node
else:
self.head = node
self.lenth += 1
往链表中的指定的位置插入结点值为data
如上图所示:
正常情况下
- 创建新的结点
- 找到索引为pos-1的结点
- 将新节点的next属性指向原来的pos结点(3和4的位置不可更换)
- 将pos-1结点的next属性指向新的结点
- 将原来的pos结点的prev属性指向新的结点
- 新节点的prev属性指向pos-1结点
- 长度加1
非正常情况下
无非就是pos超出范围加上if条件判断即可
def insert(self,pos,data):
# 往链表中的指定的位置添加一个节点值为data
if pos <= 0:
self.add(data)
elif pos > self.lenth:
self.append2(data)
else:
node = Node(data)
a = pos - 1
cur = self.head
while a : # 循环出来就是pos-1结点
cur = cur.next
a -= 1
# 将新节点的next属性指向原来的pos结点
node.next = cur.next
# 将pos-1结点的next属性指向新的结点
cur.next = node
# 将原来的pos结点的prev属性指向新的结点
cur.next.prev = node
# 新节点的prev属性指向pos - 1结点
node.prev = cur
self.lenth += 1 # 记得缩进
值得注意的是:上述代码中22行self.lenth += 1必须要缩进到else:中
因为两个if内层调用的方法本身就会带有self.lenth += 1故不用一同执行该行
删除链表中第一个值为data的结点
正常情况下:
- 找到值为data的结点cur
- 将cur的前一个结点的next属性指向cur的后一个节点
- 将cur的后一个节点的prev属性指向cur的前一个结点
- 长度减1
特殊情况下:
-
如果链表的data在头部结点
那么将self.head属性指向cur的下一个结点即可(第二个节点)
将cur的下一个结点的prev属性指向None(这步千万别忘了,虽然说不会影响程序的运行,但是会造成程序资源的浪费)
-
如果删除的结点是尾部结点的话
只用将他的前一个结点的next属性换成None即可
def remove(self,data):
# 删除第一个值为data的结点
cur = self.head
while cur:
if cur.data == data:
# 进行删除操作
if cur == self.head: # 头节点的删除方式
self.head = cur.next
else:
cur.prev.next = cur.next
if cur.next: # 尾结点的话不执行下面的代码不执行
cur.next.prev = cur.prev
self.lenth -= 1
return 0
cur = cur.next
return -1
修改链表中指定位置的值
正常情况下
- 循环遍历找到指定位置的结点
- 将指定位置的结点的data属性改为指定的data
非正常情况下
无非就是给定的索引不在范围中(加上if条件判断即可)
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的结点
通过结点对象循环遍历每一个结点 判断其值是否为data如果是 返回索引值 否则 返回None
如果对象为None,即到达尾结点,结束循环。
代码如下
def search(self,data):
# 查找链表中是否有值为data的结点
cur = self.head
count = 0
while cur:
if cur.data == data:
return f'找到该数据了,其索引为{count}'
else:
cur = cur.next
count += 1
return '未找到data的结点'
双向循环链表的python实现完整代码
# @Author :泰敢人辣
# Date :2023-07-25 18:34
class Node():
def __init__(self,data,_next = None,_prev = None):
self.prev = _prev
self.data = data
self.next = _next
class Doublelinklist():
def __init__(self):
self.head = None
self.lenth = 0
def is_empty(self):
return self.lenth == 0
def nodes_list_1(self):
'''
将链表中的所有的data添加到列表中
1.遍历循环所有的链表结点
2.将每个节点的data属性添加到列表中
3.返回列表
附加:
这里的循环遍历可以有两种方法(在此我们两种方法都采用以下)
1.通过链表本身的长度进行遍历
2.通过节点是否有内容进行遍历
:return:
'''
res = []
a = self.lenth
cur = self.head
while a:
res.append(cur.data)
cur = cur.next
a -= 1
return res
def nodes_list_2(self):
'''
通过节点是否有内容进行遍历
将链表中的所有的data添加到列表中
1.遍历循环所有的链表结点
2.将每个节点的data属性添加到列表中
3.返回列表
:return:
'''
res = []
cur = self.head
while cur:
res.append(cur.data)
cur = cur.next
return res
def add(self,data):
'''
在链表的头部添加结点
正常情况下
1.创建新的结点
2.将原来的头节点的前驱属性指向新的节点
3.将新节点的next属性指向下一节点
4.头节点指向新的结点
特殊的
当链表为空的时候
只用将head指向结点即可
:param data:
:return:
'''
node = Node(data)
if self.is_empty():
self.head = node
else:
self.head.prev = node # 让链表中原本的头结点的prev指向新建的节点
node.next = self.head
self.head = node
self.lenth += 1
def append1(self,data):
# 在链表的尾部添加结点
'''
1.创建新的结点node
2.循环遍历找到最后一个结点
3.将新节点的prev属性指向尾部结点
4.将尾部节点的next属性指向新节点
5.长度加1
附加:注意判断当链表是空链表的时候如何进行操作
创建新的结点
直接将self.head = node即可
:return:
'''
node = Node(data)
cur = self.head
if cur:
while cur.next:
cur = cur.next
node.prev = cur
cur.next = node
else:
self.head = node
self.lenth += 1
def append2(self,data):
node = Node(data)
cur = self.head
if cur:
a = self.lenth - 1
while a:
cur = cur.next
a -= 1
node.prev = cur
cur.next = node
else:
self.head = node
self.lenth += 1
def insert(self,pos,data):
# 往链表中的指定的位置添加一个节点值为data
'''
1. 创建新的结点
2. 找到索引为pos-1的结点
3. 将pos-1结点的next属性指向新的结点
4. 将新节点的next属性指向原来的pos结点
5. 将原来的pos结点的prev属性指向新的结点
6. 新节点的prev属性指向pos-1结点
7. 长度加1
注意3456的顺序有的时候需要进行更换呦
:param pro:
:param data:
:return:
'''
if pos <= 0:
self.add(data)
elif pos > self.lenth:
self.append2(data)
else:
node = Node(data)
a = pos - 1
cur = self.head
while a : # 循环出来就是pos-1结点
cur = cur.next
a -= 1
# 将新节点的next属性指向原来的pos结点
node.next = cur.next
# 将pos-1结点的next属性指向新的结点
cur.next = node
# 将原来的pos结点的prev属性指向新的结点
cur.next.prev = node
# 新节点的prev属性指向pos - 1结点
node.prev = cur
'''
注意这里需要缩进带else中,因为上面两个if的内层调用的方法会自动带有self.lenth+=1的代码
所以在这个插入方法中不用再进行执行了!!!
'''
self.lenth += 1
def remove(self,data):
# 删除第一个值为data的结点
'''
正常情况下:
1. 找到值为data的结点cur
2. 将cur的前一个结点的next属性指向cur的后一个节点
3. 将cur的后一个节点的prev属性指向cur的前一个结点
4. 长度减1
特殊情况下:
- 如果链表的data在头部结点
那么将self.head属性指向cur的下一个结点即可(第二个节点)
将cur的下一个结点的prev属性指向None(这步千万别忘了,虽然说不会影响程序的运行,但是会造成程序资源的浪费)
- 如果删除的结点是尾部结点的话
只用将他的前一个结点的next属性换成None即可
:param data:
:return:
'''
cur = self.head
while cur:
if cur.data == data:
# 进行删除操作
if cur == self.head: # 头节点的删除方式
self.head = cur.next
else:
cur.prev.next = cur.next
if cur.next: # 尾结点的话不执行下面的代码
cur.next.prev = cur.prev
self.lenth -= 1
return 0
cur = cur.next
return -1
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('索引不在范围内')
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 = Doublelinklist()
l1.add(1)
l1.add(2)
l1.add(3)
l1.add(4)
l1.add(5)
print(l1.nodes_list_1())
l1.remove(1)
print(l1.nodes_list_1())
l1.modify(0,11111)
print(l1.nodes_list_1())
print(l1.search(11111))
附件
附件1:双向链表寻找尾结点(同单向)
cur = self.head
while cur.next:
cur = cur.next
附件2:通过索引寻找指定的结点
'''
pos为想要找的结点的索引
'''
pos = 10 # 将10替换成指定的索引
cur = self.head
while pos:
cur = cur.next
pos -= 1
其他有关链表的技巧详见博主原创文章==附件内容
链接1: 单向链表Python===>寻找尾结点