介绍
基于结点的数据结构拥有独特的存取方式,因此在某些时候具有性能上的优势。
本章我们会探讨链表,它是最简单的一种基于结点的数据结构,而且也是后续内容的基础。
你会发现,虽然链表和数组看上去差不多,但在性能上却各有所长。
- 对比数组
- 与数组不同的是,组成链表的格子不是连续的。它们可以分布在内存的各个地方。这种不相
邻的格子,就叫作结点。 - 每个结点除了保存数据,它还保存着链表里的下一结点的内存地址。
效率
读取
读取链表中某个索引值的最坏情况,应该是读取最后一个索引。这种情况下,因为计算机得
从第一个结点开始,沿着链一直读到最后一个结点,于是需要 N 步。由于大 O 记法默认采用最坏
情况,所以我们说读取链表的时间复杂度为 O(N)。这跟读取数组的 O(1)相比,的确是一大劣势。
查找
对于数组和链表来说,它们都是从第一格开始逐个格子地找,直至找到。如果是最坏情况,即所找
的值在列表末尾,或完全不在列表里,那就要花 O(N)步。
插入
在某些情况下,链表的插入跟数组相比,有着明显的优势。回想插入数组的最坏情况:当插
入位置为索引 0 时,因为需要先将插入位置右侧的数据都右移一格,所以会导致 O(N)的时间复
杂度。然而,若是往链表的表头进行插入,则只需一步,即 O(1)
要在表头增加"yellow",我们只需创建一个新的结点,然后使其链接到"blue"那一结点。
其实,当如果要在中间插入一个元素,链表的插入效率为 O(N),与数组一样。因为需要先读取到前后的元素,改变其指针指向。
删除
从效率上来看,删除跟插入是相似的。如果删除的是链表的第一个结点,那就只要 1 步:将链表的 first_node 设置成当前的第二个结点。
要在链表中间做删除,计算机需要修改被删结点的前一结点的链:
使用场景
高效地遍历单个列表并删除其中多个元素,是链表的亮点之一。
- python实现
class Node:
def __init__(self, data=None):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
if not self.head:
self.head = Node(data)
else:
current = self.head
while current.next:
current = current.next
current.next = Node(data)
def display(self):
elements = []
current = self.head
while current:
elements.append(current.data)
current = current.next
print("链表元素:", elements)
# 使用场景:模拟一个简单的任务队列
tasks = LinkedList()
tasks.append("编写报告")
tasks.append("设计海报")
tasks.append("准备演讲稿")
# 显示当前任务队列
tasks.display()
# 假设完成了第一个任务
tasks.head.next = tasks.head.next.next
# 更新任务队列
tasks.display()
双向链表
链表的另一个引人注目的应用,就是作为队列的底层数据结构。
采用双向链表这一链表的变种,就能使队列的插入和删除都为 O(1)
因为双向链表能直接访问前端和末端的结点,所以在两端插入的效率都为 O(1),在两端删除的效率也为 O(1)。由于在末尾插入和在开头删除都能在 O(1)的时间内完成,因此拿双向链表作为队列的底层数据结构就最好不过了。
总结
你学会了在特定情况下使用链表来改善性能。后面还会介绍更复杂的基于结点的数据结构,它们更常用,并且对性能的提升更大。