1. 双向链表
单向链表之所以叫做单向链表,是因为它的所有链接区域都指向了后继节点,这样的缺点就是:无法找到前面的节点,如果想要找前面的节点,只能从头开始查找。
节点指向的后面的那个节点称之为:后继节点
节点指向的前面的那个节点称之为:前驱节点
之前学习的是单向链表:
现在我们需要把节点进行重新改造,改造为 双向节点:
对于双向节点来说,就有三个部分了:
- 数据区
- 后继区
- 前驱区
其中:
- n/N: Next
- p/P: Prior/Previous
一种更复杂的链表是“双向链表“或“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
- 双向链表头节点的前驱节点为None
- 双向链表尾节点后继节点为None
1.1 双向链表实现的操作
is_empty()
:链表是否为空length()
:链表长度travel()
:遍历链表add(item)
:链表头部添加元素append(item)
:链表尾部追加元素insert(pos, item)
:链表指定位置添加元素remove(item)
:删除节点search(item)
:查找节点是否存在
class Node():
"""节点"""
def __init__(self, item):
self.elem = item
self.next = None
self.prior = None
class DoubleLinkedList():
"""
双链表:对于单链表和双链表而言,
+ is_empty()
+ length()
+ travel()
+ search()
这四个方法是通用的,所以我们可以直接继承单链表(这就体现了封装的好处)
但我们一直用的是私有方法,所以按规定是不能继承的
"""
def __init__(self, node=None):
"""头节点是不向外部暴露的,因此为私有属性
如果指定node,那么头节点就确定了
如果不指定node,代表链表为空(里面没有任何节点)
"""
self.__head = node
def is_empty(self):
"""链表是否为空
只要self._head指向None,那么意味着链表为空
如果不指向None,则意味着链表不为空
"""
return self.__head == None
def length(self):
"""链表长度
current游标用来移动,遍历节点
"""
current = self.__head # cur指向的就是节点node
# count记录节点的个数
count = 0 # count初始化
while current: # 当current不为None
count += 1
current = current.next # 移动游标(指针)
return count
def travel(self):
"""遍历整个链表"""
cur = self.__head # cur指向的就是节点node
print("链表内容: [", end="")
while cur:
print(f"{
cur.elem}", end=" ")
cur = cur.next
print("]", end="\n")
def add(self, item):
"""在链表头部插入一个节点(里面包含了元素)"""
node = Node(item) # 构造存放元素的节点
node.next = self.__head # 让新节点的next指向头节点
self.__head.prior = node # 让头节点的prior指向新的节点
self.__head = node # 让头指向新的节点
def append(self, item):
"""在链表头部插入一个节点(里面包含了元素)
想要在尾部插入,首先需要一个指针 -> 找到尾节点
"""
node = Node(item) # 构造存放元素的节点
if self.is_empty():
self.__head = node # 特殊情况
else:
cur = self.__head
while cur.next:
cur = cur.next
cur.next = node
node.prior = cur
# node.next = None -> 没必要,因为node初始化后,node.next就是等于None
def insert(self, pos, item):
if pos <= 0: # 说明是头插,直接调用前面实现的方法即可
self.add(item)
elif pos > (self.length() - 1): # 尾插法
self.append(item)
else:
"""
之前我们为了找到指定位置,使用了两个指针,现在双链表就不需要两个指针了
"""
node = Node(item)
cur = self.__head
count = 0
while count < pos:
count += 1
cur = cur.next
# 当循环退出后, cur指向pos位置
# 方法1
node.next = cur
node.prior = cur.prior
cur.prior.next = node
cur.prior = node
# 方法2
"""
node.next = cur
node.prior = cur.prior
cur.prior = node
cur.prior.next = node
"""
def remove(self, item):
"""
特殊情况:
1. 需删除的节点为头节点
2. 原有的链表只有一个节点
3. 要删除的节点为尾节点
"""
cur = self.__head
while cur:
if cur.elem == item: # 找到该元素并删除
if cur == self.__head: # 需删除的元素为头节点
self.__head = cur.next
if cur.next: # 如果原有的链表只有一个节点,如果是,不加判断就会报错
cur.next.prior = None
else:
pass # 已经是None了
else: # 需删除的不是头节点
cur.prior.next = cur.next # 指针的上一个node.next指向cur后面的node
if cur.next: # 如果要删除的节点为尾节点,那么cur.next.prior就会报错
cur.next.prior = cur.prior # cur后面的prior指向cur前面的
else: # cur.next is None -> 不用管了
pass
break
else:
cur = cur.next # 指针继续
def search(self, item):
"""查找节点是否存在: 就是查找指定的节点node是否在链表中
存在:True
不存在:False
满足cur==None的特殊情况
"""
cur = self.__head
while cur:
if cur.elem == item:
return True
else:
cur = cur.next
return False
if __name__ == "__main__":
# 创建一个空链表 dll -> Double Linked List
dll = DoubleLinkedList()
print(f"链表是否为空: {
dll.is_empty()}")
print(f"链表的长度: {
dll.length()}")
print("-" * 50)
# 往链表中追加元素
dll.append(1)
print(f"链表是否为空: {
dll.is_empty()}")
print(f"链表的长度: {
dll.length()}")
# 往链表中追加元素
dll.append(2)
dll.append(3)
dll.append(4)
dll.append(5)
print(f"链表是否为空: {
dll.is_empty()}")
print(f"链表的长度: {
dll.length()}")
# 往链表的头部添加元素
dll.add(100)
# 遍历链表
dll.travel()
# 在指定位置添加元素
dll.insert(pos=-2, item=9)
dll.travel()
dll.insert(pos=5, item=321)
dll.travel()
dll.insert(pos=100, item=123456)
dll.travel()
# 删除元素
dll.remove(9)
dll.travel()
dll.remove(123456)
dll.travel()
结果:
链表是否为空: True
链表的长度: 0
--------------------------------------------------
链表是否为空: False
链表的长度: 1
链表是否为空: False
链表的长度: 5
链表内容: [100 1 2 3 4 5 ]
链表内容: [9 100 1 2 3 4 5 ]
链表内容: [9 100 1 2 3 321 4 5 ]
链表内容: [9 100 1 2 3 321 4 5 123456 ]
链表内容: [100 1 2 3 321 4 5 123456 ]
链表内容: [100 1 2 3 321 4 5 ]
2. 单向循环链表
单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点。
2.1 单向循环链表实现的方法
is_empty()
:链表是否为空length()
:链表长度travel()
:遍历链表add(item)
:链表头部添加元素append(item)
:链表尾部追加元素insert(pos, item)
:链表指定位置添加元素remove(item)
:删除节点search(item)
:查找节点是否存在
class Node():
def __init__(self, item):
self.elem = item
self.next = None
class SingleCirclarLinkedList():
"""单向循环链表"""
def __init__(self, node=None):
self.__head = node
if node:
node.next = node # 指向自己(Circular的特点)
def is_empty(self):
return self.__head is None
def length(self):
"""
特殊情况:
1. 链表为空
2. 只有一个node -> 满足
"""
if self.is_empty():
return 0
cur = self.__head
count = 1 # 为了正确计数,count = 1
while cur.next != self.__head:
count += 1
cur = cur.next
return count
def travel(self):
"""
特殊情况:
1. 链表为空
2. 链表只有一个node -> 满足
"""
if self.is_empty(): # case_1
return
cur = self.__head
print("[", end="")
while cur.next != self.__head:
print(cur.elem, end=" ")
cur = cur.next
# 退出循环,cur指向尾节点,但尾节点的元素并未打印
print(cur.elem, end="")
print("]")
def add(self, item):
"""
还需要将尾节点的next指向新node -> 需要一个指针
思路1:先找到尾节点,将尾节点的next指向新节点,再操作其他
思路2:先操作新节点,最后再找尾节点
特殊情况:
1. 链表为空
2. 链表只有一个节点 -> 满足
"""
node = Node(item)
if self.is_empty(): # case_1
self.__head = node
node.next = node
else:
cur = self.__head
# 方法1 —— 先找到尾节点
while cur.next != self.__head:
cur = cur.next
# 结束循环后,cur指向尾节点
node.next = self.__head
self.__head = node
cur.next = node # 或者写成cur.next = self.__head
# # 方法2 —— 先处理新node,再重新找尾节点
# node.next = self.__head
# self.__head = node
# while cur.next != node.next:
# cur = cur.next
# # 结束循环后,cur指向尾节点
# cur.next = node
def append(self, item):
"""
特殊情况:
1. 链表为空
2. 只有一个元素 -> 满足
"""
node = Node(item)
if self.is_empty():
self.__head = node
node.next = node
else:
cur = self.__head
while cur.next != self.__head:
cur = cur.next
# 退出循环,cur指向尾节点
node.next = self.__head
cur.next = node
def insert(self, pos, item):
if pos <= 0: # 头插法
self.add(item)
elif pos > (self.length() - 1): # 尾插法
self.append(item)
else:
"""
对于中间插入,不影响到尾部链接头部 <=> 单链表的insert方法
"""
prior = self.__head
count = 0
while count < (pos - 1):
count += 1
prior = prior.next
# 退出循环,prior指向pos-1位置
node = Node(item)
node.next = prior.next
prior.next = node
def remove(self, item):
"""
只有涉及到头节点或尾节点时,才需要遍历(因为尾节点需链接到头节点)
"""
if self.is_empty(): # 链表为空
return
cur = self.__head
prior = None
while cur.next != self.__head:
if cur.elem == item:
# 判断情况
if cur == self.__head: # 头节点
rear = self.__head
while rear.next != self.__head:
rear = rear.next
# rear指向尾节点
self.__head = cur.next
rear.next = self.__head
else: # 中间节点(不涉及到尾节点重新链接问题)
prior.next = cur.next
# 已经找到了,循环退出(实际是方法退出)
return
else: # 这次没有找到,指针移动
prior = cur
cur = cur.next
# 如果删除的节点是尾节点(尾节点不会进循环体,需单独处理)
# 退出循环,cur指向尾节点
if cur.elem == item: # 尾节点或者只有一个节点
if cur == self.__head: # 链表只有一个节点
self.__head = None
else:
prior.next = cur.next
def search(self, item):
"""
特殊情况:
1. 链表为空
2. 只有一个node -> 满足
"""
if self.is_empty(): # Case_1: 空链表肯定没有元素
return False
cur = self.__head
while cur.next != self.__head:
if cur.elem == item:
return True
else:
cur = cur.next
# 循环走完,cur指向尾节点,但没有让尾节点的数据于item进行比较
if cur.elem == item: # 尾节点数据与item进行判断
return True
else:
return False
if __name__ == "__main__":
# 创建一个空链表
scll = SingleCirclarLinkedList()
print(f"链表是否为空: {
scll.is_empty()}")
print(f"链表的长度: {
scll.length()}")
# 往链表中追加元素
scll.append(1)
print(f"链表是否为空: {
scll.is_empty()}")
print(f"链表的长度: {
scll.length()}")
# 往链表中追加元素
scll.append(2)
scll.append(3)
scll.append(4)
scll.append(5)
print(f"链表是否为空: {
scll.is_empty()}")
print(f"链表的长度: {
scll.length()}")
# 往链表的头部添加元素
scll.add(100)
# 遍历链表
scll.travel()
# 在指定位置添加元素
scll.insert(pos=-2, item=9)
scll.travel()
scll.insert(pos=5, item=321)
scll.travel()
scll.insert(pos=100, item=123456)
scll.travel()
# 删除元素
scll.remove(9)
scll.travel()
scll.remove(123456)
scll.travel()
结果:
链表是否为空: True
链表的长度: 0
链表是否为空: False
链表的长度: 1
链表是否为空: False
链表的长度: 5
[100 1 2 3 4 5]
[9 100 1 2 3 4 5]
[9 100 1 2 3 321 4 5]
[9 100 1 2 3 321 4 5 123456]
[100 1 2 3 321 4 5 123456]
[100 1 2 3 321 4 5]
3. 链表的总结
- 单向链表
- 双向链表
- 单向循环链表
现在我链表的部分就讲完了,其实如果还要扩充的话:
- 可以将双链表扩充为双向循环链表。
- 在将顺序表时,有一个表头信息。那么对于链表来说,是否可以做成 包含表头信息的链表?
在学习链表的过程中,代码是其次的,主要是学习指针移动的思想。