1. 链表
"""
背景:
列表在存储数据的时候, 需要使用到连续的空间, 如果空间不够, 就会导致扩容失败, 针对于这种情况, 可以通过链表实现
链表在内存中存储的时候, 可以不是连续的空间, "有地儿就行", 所以: 增删相对更好操作
链表介绍:
概述:
属于线性结构, 即: 每个节点(除头没有前置节点,尾部没有后置节点)都只有1个前置节点 和 1个后置节点
组成:
链表是由节点组成的, 根据节点的不同, 链表又分为: 单向链表, 单向循环链表, 双向链表, 双向循环链表
节点划分:
单向链表, 单向循环链表:
节点是由 1个数值域 和 1个地址域组成, 也叫: 元素域 和 链接域组成
双向链表, 双向循环链表:
节点是由 1个数值域 和 2个地址域组成, 也叫: 元素域 和 链接域组成
划分:
单向链表: 节点是由 1个数值域 和 1个地址域组成, 最后1个节点的地址域为: None
单向循环链表: 节点是由 1个数值域 和 1个地址域组成, 最后1个节点的地址域为: 第1个节点的 地址
双向链表: 节点是由 1个数值域 和 2个地址域组成, 分别指向前一个节点 和 后一个节点的地址, 第1个节点的前地址域 和 最后1个节点的后地址域为 None
双向循环链表: 1个数值域, 2个地址域. 第1个节点的前地址域指向最后1个节点的地址, 最后1个节点的后地址域指向第1个节点的 地址
"""
1.1 单向链表
"""
需求: 通过面向对象思维, 实现自定义链表
分析流程:
节点类: SingleNode
属性:
item 代表: 数值域
next 代表: (下个节点的)地址域
单向链表类: SingleLinkedList:
属性:
head 代表: 链表的第一个节点(头结点), 如无, 则为: None
行为:
isEmpty(self) 链表是否为空
length(self) 链表长度
showAll(self) 遍历整个链表
addFront(self, item) 链表头部添加元素
append(self, item) 链表尾部添加元素
insert(self, idx, item) 指定位置添加元素
remove(self, item) 删除节点
contains(self, item) 查找节点是否存在
"""
class SingleNode():
"""
创建 单向链表 节点 对象
"""
def __init__(self, item):
"""
创建SingleNode对象时,自动调用该方法,初始化对象属性
:param item:
"""
# 节点数据
self.item = item
# next 指针指向下一个节点
self.next = None
def __str__(self):
"""
print(SingleNode)对象时自动调用,返回对象属性值
:return:
"""
return f'item={self.item}'
class SingleLinkedList():
"""
创建 单向链表 对象
"""
# 初始化属性, 用 head属性, 指向: 链表的头结点
def __init__(self, node=None):
self.head = node
# isEmpty(self) 链表是否为空
def isEmpty(self):
"""
判断链表是否为空.
:return: True => 为空, False => 不为空
"""
return self.head is None
# length(self) 链表长度
def length(self):
# 定义变量 count = 0 表示链表的长度
count = 0
# 定义变量 cur, 表示: 当前节点, 它是从 头结点开始 往下逐个获取的
cur = self.head
# 循环往后获取每个节点即可, 只要 cur不为None, 就说明还有节点
while cur is not None:
# 走这里, 说明有节点, 计数器加1, 然后设置 cur为 当前节点的下个节点即可
count += 1
cur = cur.next
return count
# showAll(self) 遍历整个链表, 打印每个节点的 数值域(的值)
def showAll(self):
# 定义变量 cur, 表示: 当前节点, 它是从 头结点开始 往下逐个获取的
cur = self.head
# 循环往后获取每个节点即可, 只要 cur不为None, 就说明还有节点
while cur is not None:
# 走这里, 说明有节点, 保存当前节点的 元素域(数值域) item属性
yield cur.item
# 重新设置 cur 为: 当前节点的下个节点
cur = cur.next
# addFront(self, item) 链表头部添加元素
def addFront(self, item):
# 把 item 封装成 节点
new_node = SingleNode(item)
# 设置 新节点的地址域 指向 之前旧的头结点
new_node.next = self.head
# 设置新节点为: 新的头结点
self.head = new_node
# append(self, item) 链表尾部添加元素
def append(self, item):
# 把 item 封装成 节点
new_node = SingleNode(item)
# 判断链表是否为空, 如果为空, 则: 新节点充当头结点即可
if self.isEmpty():
# 为空, 新节点充当头结点即可
self.head = new_node
else:
# 不为空, 找到 最后1个节点, 设置它的 地址域为 新节点即可
# cur代表当前的节点, 从头结点往后 逐个获取
cur = self.head
while cur.next is not None:
# 获取当前节点的 下个节点
cur = cur.next
# 走这里, cur就是最后1个节点, 设置它的地址域为: 新节点即可
cur.next = new_node
# insert(self, idx, item) 指定位置添加元素
def insert(self, idx, item):
"""
往指定的位置, 添加元素
:param idx: 要添加元素到的 位置(索引)
:param item: 具体的要添加的元素
:return: 无
"""
# 如果 idx 的值 <= 0, 就往: 头部添加
if idx <= 0:
self.addFront(item)
# 如果 idx 的值 >= 链表的长度, 就往: 尾部添加
elif idx >= self.length():
self.append(item)
# 走到这里, 说明, 插入位置, 是在链表中间. 需要找到 插入位置前的哪个节点
else:
# 定义变量 count 用于表示: 插入位置前的那个元素的 索引
count = 0
# 定义变量 cur, 表示: 当前元素(节点), 找到: 插入位置前的哪个节点
cur = self.head
# 遍历, 判断: 只要 count的值 < idx - 1要小, 就一直循环 循环结束后: count = idx - 1
while count < idx - 1:
count += 1
cur = cur.next
# 设置 新节点的地址域为: cur节点的地址域
new_node = SingleNode(item)
new_node.next = cur.next
# 设置 cur节点的地址域为: 新节点(的地址)
cur.next = new_node
# remove(self, item) 删除节点
def remove(self, item):
# 定义变量 cur, 记录: 当前的节点, 默认: 从头结点开始
cur = self.head
# 定义变量 pre, 记录: 当前节点的 前1个节点(前置节点)
pre = None
# 遍历, 获取到每个节点
while cur is not None:
# 判断当前节点是否是要被删除的节点, 即: 当前节点的 数值域 是否等于 item参数
if cur.item == item:
# 走这里, 说明 cur就是要被删除的节点
# 有可能要删除的节点是: 头结点
if cur == self.head:
# cur的下个节点作为新的 头结点即可
self.head = cur.next
# 断开旧的头结点 和 后续节点的链接
cur = None
else:
# 要删除的节点不是头结点
pre.next = cur.next
# 断开当前节点(要被删除的节点) 和 后续节点的链接
cur = None
# 核心细节: 删除之后, 程序结束
break
else:
# 走这里, 说明 cur不是要被删除的节点, 继续往后拿
# pre表示刚才判断过的节点
pre = cur
# 更新cur为它的下个节点
cur = cur.next
# contains(self, item) 查找节点是否存在
def contains(self, item):
# 定义cur变量, 从头结点开始往后找
cur = self.head
# 循环获取到每个节点
while cur is not None:
# 判断当前节点的 元素域(数值域) 是否 和 要查找的值 一致
if cur.item == item:
# 找到了
return True
# 不匹配, 就开始校验下个节点
cur = cur.next
# 走到这里, 整个while都结束了, 即: 没有找到
return False
if __name__ == '__main__':
sll = SingleLinkedList()
sll.append('张飞')
sll.append('刘备')
sll.append('关羽')
sll.addFront('曹操')
sll.insert(2, '郭嘉')
print(f'链表中是否存在刘备: {sll.contains("刘备")}')
sll.remove('刘备')
print(f'链表中是否存在刘备: {sll.contains("刘备")}')
for i in sll.showAll():
print(i)
控制台输出
链表中是否存在刘备: True
链表中是否存在刘备: False
曹操
张飞
郭嘉
关羽
1.2 双向链表
class DualNode():
"""
创建 双向链表 节点 对象
"""
def __init__(self, item):
"""
创建DualNode对象时,自动调用该方法,初始化对象属性
:param item:
"""
# 节点数据
self.item = item
# next 指针指向下一个节点
self.next = None
# pre 指针指向上一个节点
self.pre = None
def __str__(self):
"""
print(DualNode)对象时自动调用,返回对象属性值
:return:
"""
return f'item={self.item}'
class DualLinkedList():
"""
创建 双向链表 节点 对象
"""
def __init__(self, node=None):
self.head = node
self.tail = node
self.size = 0 if node is None else 1
# isEmpty(self) 链表是否为空
def isEmpty(self):
return self.head is None
# length(self) 链表长度
def length(self):
return self.size
# showAll(self) 遍历整个链表, 打印每个节点的 数值域(的值)
def showAll(self, reverse=False):
"""
reverse True 从尾部开始遍历, False 从头部开始遍历
:param reverse:
:return: 链表数据生成器
"""
if reverse:
# 定义变量 cur, 表示: 当前节点, 它是从 尾结点开始 往下逐个获取的
cur = self.tail
# 循环往前获取每个节点即可, 只要 cur不为None, 就说明还有节点
while cur is not None:
# 走这里, 说明有节点, 保存当前节点的 元素域(数值域) item属性
yield cur.item
# 重新设置 cur 为: 当前节点的上个节点
cur = cur.pre
else:
# 定义变量 cur, 表示: 当前节点, 它是从 头结点开始 往下逐个获取的
cur = self.head
# 循环往后获取每个节点即可, 只要 cur不为None, 就说明还有节点
while cur is not None:
# 走这里, 说明有节点, 保存当前节点的 元素域(数值域) item属性
yield cur.item
# 重新设置 cur 为: 当前节点的下个节点
cur = cur.next
# addFront(self, item) 链表头部添加元素
def addFront(self, item):
# 把 item 封装成 节点
new_node = DualNode(item)
if self.isEmpty():
# 为空, 新节点充当头尾结点即可
self.head = new_node
self.tail = new_node
else:
new_node.next = self.head
self.head.pre = new_node
# 设置 新节点的地址域 指向 之前旧的头结点
self.head = new_node
self.size += 1
# append(self, item) 链表尾部添加元素
def append(self, item):
# 把 item 封装成 节点
new_node = DualNode(item)
if self.isEmpty():
# 为空, 新节点充当头尾结点即可
self.head = new_node
self.tail = new_node
else:
new_node.pre = self.tail
self.tail.next = new_node
# 设置 新节点的地址域 指向 之前旧的尾结点
self.tail = new_node
self.size += 1
# insert(self, idx, item) 指定位置添加元素
def insert(self, idx, item):
"""
往指定的位置, 添加元素
:param idx: 要添加元素到的 位置(索引)
:param item: 具体的要添加的元素
:return: 无
"""
# 如果 idx 的值 <= 0, 就往: 头部添加
if idx <= 0:
self.addFront(item)
# 如果 idx 的值 >= 链表的长度, 就往: 尾部添加
elif idx >= self.length():
self.append(item)
# 走到这里, 说明, 插入位置, 是在链表中间. 需要找到 插入位置前的哪个节点
else:
# 定义变量 count 用于表示: 插入位置前的那个元素的 索引
count = 0
# 定义变量 cur, 表示: 当前元素(节点), 找到: 插入位置前的哪个节点
cur = self.head
# 遍历, 判断: 只要 count的值 < idx - 1要小, 就一直循环 循环结束后: count = idx - 1
while count < idx - 1:
count += 1
cur = cur.next
new_node = DualNode(item)
# 设置 新节点的地址域为: cur节点的地址域
new_node.next = cur.next
cur.next.pre = new_node
# 设置 cur节点的地址域为: 新节点(的地址)
cur.next = new_node
new_node.pre = cur
self.size += 1
# remove(self, item) 删除节点
def remove(self, item):
# 定义变量 cur, 记录: 当前的节点, 默认: 从头结点开始
cur = self.head
# 定义变量 pre, 记录: 当前节点的 前1个节点(前置节点)
preNode = None
# 遍历, 获取到每个节点
while cur is not None:
# 判断当前节点是否是要被删除的节点, 即: 当前节点的 数值域 是否等于 item参数
if cur.item == item:
# 走这里, 说明 cur就是要被删除的节点
# 有可能要删除的节点是: 头结点
if cur == self.head:
# cur的下个节点作为新的 头结点即可
self.head = cur.next
# 断开旧的头结点 和 后续节点的链接
if self.head is not None:
cur.next.pre = None
else:
# 头节点为空 也把尾节点置空
self.tail = None
elif cur == self.tail:
self.tail = preNode
preNode.next = None
else:
# 要删除的节点不是头结点
preNode.next = cur.next
cur.next.pre = preNode
# 核心细节: 删除之后, 程序结束
break
else:
# 走这里, 说明 cur不是要被删除的节点, 继续往后拿
# pre表示刚才判断过的节点
preNode = cur
# 更新cur为它的下个节点
cur = cur.next
self.size -= 1
# contains(self, item) 查找节点是否存在
def contains(self, item):
# 定义cur变量, 从头结点开始往后找
cur = self.head
# 循环获取到每个节点
while cur is not None:
# 判断当前节点的 元素域(数值域) 是否 和 要查找的值 一致
if cur.item == item:
# 找到了
return True
# 不匹配, 就开始校验下个节点
cur = cur.next
# 走到这里, 整个while都结束了, 即: 没有找到
return False
if __name__ == '__main__':
sll = DualLinkedList()
sll.append('张飞')
sll.insert(1, '张飞1')
sll.append('张飞2')
sll.append('张飞3')
sll.append('张飞4')
sll.addFront('张飞0')
print(f'链表中是否存在张飞: {sll.contains("张飞")} 链表长度: {sll.length()}')
sll.remove('张飞')
print(f'链表中是否存在张飞: {sll.contains("张飞")} 链表长度: {sll.length()}')
for i in sll.showAll():
print(i)
print('*' * 30)
for i in sll.showAll(True):
print(i)
控制台输出
链表中是否存在张飞: True 链表长度: 7
链表中是否存在张飞: False 链表长度: 6
张飞0
张飞1
张飞2
张飞3
张飞4
******************************
张飞4
张飞3
张飞2
张飞1
张飞0