三十功名尘与土,八千里路云和月
文章目录
链表 (Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
链表中的基本要素:
- 结点(也叫节点或元素),每一个结点有两个域,左边部分叫
数据域
,用于存放用户数据;右边叫指针域
,一般是存储着到下一个元素的指针 - head结点,一个特殊的结点,永远指向第一个结点
- tail结点,一个特殊的结点,永远指向最后一个结点
- None,链表中最后一个结点指针域的指针指向 None 值,因此也叫
接地点
- 必须明确指定链表第一项的位置,一旦我们知道第一项在哪里,第一项可以告诉我们第二项。依此类推,按照一个方向遍历,直到最后一项
- 这些节点在逻辑上相连的,但它们在物理内存上并不相连
Python 手动实现链表
接下来我们就是用 Python 来实现一个基础的 单向链表,在 C / C++ 中,通常采用 “指针+结构体” 来实现链表,而在 Python 中,我们可以采用 “引用+类” 来实现链表。
除了简单的链表结构,我们还需要实现如下功能:
is_empty()
测试链表是否为空iter()
遍历链表size()
返回链表的节点数search()
查找链表中某节点append()
在尾部增加一个节点add()
在头部增加一个节点remove()
将第一个具有传入值的节点从链表中删除
定义 Node节点类
节点 (Node)是实现链表的基本模块,每个节点至少包含两个重要部分。首先,包括节点自身的数据,称为 “数据域”,其次,每个节点包括下一个节点的 “引用”
class Node:
def __init__(self, data):
self.data = data # 节点值
self.next = None # 指向下一个节点,默认None
def __repr__(self):
return f'<node data="{self.data}">'
创建一个 Node 对象试试:
n = Node('100')
print(n) # <node data="100">
print(n.data) # 100
print(n.next) # None
此时节点对象数据为 '100'
,指针指向为 None
链表类
class Linkedlist:
def __init__(self, node=None):
self.head = node
在不传入 node 参数的情况下,此类实例后会生成一个空链表对象,初始化了 head
和 tail
节点,且两节点都指向 None
,实例化代码如下:
linked = Linkedlist()
is_empty 方法
is_empty
方法检查链表是否是一个空链表,这个方法只需要检查 head
节点是否指向 None
即可,代码如下:
def is_empty(self):
"""检测链表是否为空"""
return self.head is None
add 方法
add
方法在链表前端添加节点
- 检测链表是否为空
- 若为空,则直接设置 head 引用新节点
- 若非空,首先设置 head 引用新节点,随后将新节点的下一个引用指向旧链表的第一个节点
def add(self, item):
"""添加节点到链表前端"""
node = Node(item) # 创建节点
if self.is_empty():
self.head = node # 链表为空, 直接修改
else:
# 链表非空,修改 head指向 及 新节点的指向
self.head, node.next = node, self.head
size方法
size
方法用于获取链表中节点数量
def size(self):
"""获取链表长度"""
count = 0 # 计数器,统计链表长度
cursor = self.head # 游标, 用于循环遍历链表
while cursor is not None:
count += 1
cursor = cursor.next # 更新游标位置
return count
iter 方法
iter
方法用于遍历链表,思路同上
def iter(self):
"""遍历链表内容"""
cursor = self.head # 游标
while cursor is not None:
yield cursor
cursor = cursor.next # 更新游标位置
append 方法
append
方法用于在链表尾部添加节点
- 检测链表是否为空
- 若为空,则直接设置 head 引用新节点
- 若非空,遍历链表至最后一个节点,将该节点指向新节点
def append(self, item):
"""添加节点到链表末尾"""
node = Node(item) # 创建节点
if self.is_empty():
self.head = node # 为空,则直接修改
else:
cursor = self.head # 游标
while cursor.next is not None:
cursor = cursor.next # 更新游标位置
cursor.next = node
search 方法
search
方法用于检索链表中指定节点并返回
def search(self, item):
"""检索指定节点并返回"""
if self.is_empty():
return False
else:
cursor = self.head
while cursor is not None:
if cursor.data == item:
return cursor # 存在则返回
cursor = cursor.next
else:
return False # 不存在返回 False
remove 方法
remove
将第一个具有传入值的节点从链表中删除
实现思路:
- 检测链表是否为空
- 若为空,直接返回 False
- 若非空,遍历链表
- 该节点为 头部节点 …
- 该节点为 尾部节点 …
- 中间节点
def remove(self, item):
"""将第一个具有传入值的节点从链表中删除"""
if self.is_empty():
return False # 链表为空, 直接返回 False
else:
cursor = self.head
prev = None # 记录上一个节点
while cursor is not None:
if cursor.data == item:
if cursor == self.head: # 若为 头部节点
self.head = cursor.next
elif cursor.next == None: # 若为 尾部节点
prev.next = None
else: # 中间节点
prev.next = cursor.next
return True
# 更新游标
prev = cursor
cursor = cursor.next
else:
return False
链表 & 列表区别
-
内存分布
列表是一块连续的内存,而链表可以不是连续内存,链表的节点与节点之间通过指针来联系
-
插入删除性能
链表与列表最大的不同在于删除,插入的性能优势。由于链表是非连续的内存,所以不用像列表一样在插入,删除操作时需要进行大面积的成员位移
例如在 a,b 节点之间插入一个新节点 c,链表只需要:
- a 断开指向 b 的执行,将指针指向 c
- c 节点将指针指向 b
这个插入操作仅仅需要移动一下指针即可,插入,删除的时间复杂度只有 O(1)
-
读取性能
链表相比之下也有劣势,那就是读取操作远不如列表,列表的读取操作之所以高效,因为它是一块连续的内存,读取的时候可以通过寻址公式快速定位,而链表由于非连续内存,所以必须通过指针一个一个节点遍历
应用场景
- 撤销功能,这种操作常见于各种文本,图形编辑器中,撤销重做在编辑器场景下属于家常便饭,单向链表由于良好的删除特性在这个场景很适用
- 实现 图,hashMap 等一些高级数据结构