Tracks
链表是一种在数据组织与管理中至关重要的数据结构。它包含一系列存储在内存随机位置的节点,有助于高效进行内存管理。链表中的每个节点包含两个主要组成部分:数据部分,以及指向序列中下一个节点的引用。
如果您第一眼觉得这个概念有点复杂,别担心!
我们将从基础讲起,解释什么是链表、为什么要使用链表,以及它所具备的独特优势。
为什么使用链表?
链表的出现是为了解决在常规列表和数组中存储数据所带来的多种局限,如下所示:
插入与删除更容易
在列表中,若要在末尾之外的任意位置插入或删除元素,需要将其后所有元素移动到新的位置。该过程的时间复杂度为 O(n),会显著影响性能,尤其在列表规模增长时。如果您还不熟悉列表的工作方式或其实现,可阅读我们的Python 列表教程。
而链表的运作方式不同。它将元素存放在多个不连续的内存位置,并通过指针将节点相连。这种结构允许链表只需修改链接即可在任意位置添加新元素或跳过被删除的元素,从而完成增删操作。
一旦您直接持有插入或删除位置处节点的引用,操作本身是 O(1)。但要找到该位置仍需 O(n) 的遍历,因此只有在您已经持有相关节点的指针(例如在链表头部操作)时,O(1) 的优势才适用。
动态大小
Python 列表本质上是动态数组,意味着它们支持灵活调整大小。
不过,这一过程涉及一系列复杂操作,包括将数组重新分配到更大的内存块。这样的重分配效率较低,因为需要将元素复制到新内存块,且可能分配出超出当前所需的空间。
相比之下,链表可在无需重分配或调整大小的情况下动态增长或收缩,这使其在需要高度灵活性的任务中更为合适。
内存效率
列表会为所有元素在一块连续内存中分配空间。如果列表需要超出最初大小,就必须分配一块新的、更大的连续内存,并将所有现有元素复制到新内存中。该过程既耗时又低效,尤其是对大型列表而言。另一方面,如果最初估计的列表大小偏大,未被使用的内存就被浪费了。
相较之下,链表为每个元素单独分配内存。由于新元素的内存可在添加时即时分配,这种结构带来更好的内存利用率。
何时应使用链表?
虽然链表相较常规列表和数组在动态大小与内存效率方面具备一些优势,但也存在局限。由于每个元素都需要存储指向下一个节点的指针,使用链表时每个元素的内存开销更高。同时,这种数据结构不支持对数据的直接访问。访问某个元素需要从链表起始处顺序遍历,搜索的时间复杂度为 O(n)。
选择使用链表还是数组取决于具体应用需求。链表最有用的场景包括:
- 您需要频繁插入和删除大量元素
- 数据规模不可预测或可能经常变化
- 不要求对元素进行直接随机访问
- 数据集中包含大型元素或复杂结构
链表的类型
链表主要有三种类型,每种在不同场景中各有优势:
单向链表

单向链表
单向链表是最简单的链表类型,每个节点包含数据以及指向序列中下一个节点的引用。它只能沿一个方向遍历——从头节点(第一个节点)到尾节点(最后一个节点)。
单向链表中的每个节点通常包含两部分:
- 数据:节点中存储的实际信息。
- 后继指针:指向下一个节点的引用。最后一个节点的后继指针通常设为 null。
由于这种数据结构只能沿单一方向遍历,按值或索引访问特定元素需要从头节点开始,依次经过各节点直到找到目标节点。该操作的时间复杂度为 O(n),因此在处理大型列表时效率较低。
在单向链表表头插入或删除节点非常高效,时间复杂度为 O(1)。然而,在中间或末尾进行插入与删除需要遍历到相应位置,时间复杂度为 O(n)。
单向链表的设计使其在需要在表头进行操作的场景中尤为实用。
双向链表

双向链表
单向链表的一个缺点是只能沿一个方向遍历,无法在需要时回到前一个节点。这一限制使得需要双向导航的操作变得困难。
双向链表通过在每个节点中加入一个额外指针来解决这一问题,从而支持双向遍历。双向链表中的每个节点包含三个元素:数据、指向下一个节点的指针,以及指向前一个节点的指针。
循环链表

循环链表
循环链表是一种特殊的链表形式,其中最后一个节点会指回第一个节点,形成一个环。这意味着与前文提到的单向和双向链表不同,循环链表没有终点,而是不断循环。
循环链表的周期性特征非常适合需要连续循环的场景,例如棋盘游戏中从最后一名玩家回到第一名玩家,或在计算机算法中用于时间片轮转(round-robin)调度等。
时间复杂度概览
快速对比链表与 Python 列表:
| 操作 | 单向链表 | 数组/Python 列表 |
|---|---|---|
| 按索引访问 | O(n) | O(1) |
| 按值搜索 | O(n) | O(n) |
| 在开头插入 | O(1) | O(n) |
| 在末尾插入 | O(n) | 均摊 O(1) |
| 在中间插入 | O(n) | O(n) |
| 在开头删除 | O(1) | O(n) |
| 在末尾删除 | O(n) | 均摊 O(1) |
要点总结:链表在表头的插入与删除上占优(O(1)),但在其他方面处于劣势。如果您并不经常在数据结构的开头添加或移除元素,常规的 Python 列表很可能是更好的选择。
如何在 Python 中创建链表
现在我们已经了解了链表的概念、用途与变体,接下来将在 Python 中实现这些数据结构。您也可以在此 DataLab 工作簿中获取本教程的笔记本;创建副本后即可编辑并运行代码。如果您在本地运行代码遇到问题,这是一个很好的选择!
初始化节点
如前所述,节点是链表中的元素,保存数据并引用序列中的下一个节点。以下是在 Python 中定义节点的方法:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def __repr__(self):
return f"Node({self.data})"
上述代码通过两步来初始化节点:“data”属性被赋予一个值,表示节点要包含的实际信息;“next”属性代表下一个节点的地址,当前设为 None,表示尚未链接到链表中的其他节点。随着我们继续向链表中添加新节点,该属性将更新为指向后继节点。
创建链表类
接下来需要创建链表类。它将封装管理节点的所有操作,例如插入与移除。我们先来初始化链表:
class LinkedList:
def __init__(self):
self.head = None # Initialize head as None
将 self.head 设为 None 表示链表初始为空,没有任何节点可供指向。接下来我们将通过插入新节点来填充该链表。
在链表开头插入新节点
在 LinkedList 类中,我们将添加一个方法来创建新节点并将其放置在链表开头:
def insertAtBeginning(self, new_data):
new_node = Node(new_data) # Create a new node
new_node.next = self.head # Next for new node becomes the current head
self.head = new_node # Head now points to the new node
每次调用上述方法,都会创建包含您指定数据的新节点。该新节点的 next 指针将被设置为当前的头节点,从而把该节点置于现有节点之前。最后,将新创建的节点设为链表的头节点。
为了更好地理解插入操作的工作机制,我们将用一系列单词来填充该链表。为此,先创建一个用于遍历并打印链表内容的方法:
def printList(self):
temp = self.head # Start from the head of the list
while temp:
print(temp.data,end=' ') # Print the data in the current node
temp = temp.next # Move to the next node
print() # Ensures the output is followed by a new line
上述方法将打印链表的内容。现在让我们使用已定义的方法将以下单词插入链表:“the quick brown fox”。
if __name__ == '__main__':
# Create a new LinkedList instance
llist = LinkedList()
# Insert each letter at the beginning using the method we created
llist.insertAtBeginning('fox')
llist.insertAtBeginning('brown')
llist.insertAtBeginning('quick')
llist.insertAtBeginning('the')
# Now 'the' is the head of the list, followed by 'quick', then 'brown' and 'fox'
# Print the list
llist.printList()
上述代码应输出如下结果:
"the quick brown fox"
在链表末尾插入新节点
现在我们将在 LinkedList 类中创建一个名为 insertAtEnd 的方法,用于在链表末尾创建新节点。如果链表为空,新节点将成为头节点;否则,它将被追加到当前最后一个节点之后。让我们看看其具体实现:
def insertAtEnd(self, new_data):
new_node = Node(new_data)
if self.head is None:
self.head = new_node
return
last = self.head
while last.next:
last = last.next
last.next = new_node
该方法首先创建一个新节点。随后检查链表是否为空;如果为空,则将新节点设为头节点。否则,遍历链表找到最后一个节点,并将该节点的指针指向新节点。
我们现在需要把该方法加入 LinkedList 类,并用它在链表末尾添加一个单词。为此,请将主函数修改为如下所示:
if __name__ == '__main__':
llist = LinkedList()
# Insert words at the beginning
llist.insertAtBeginning('fox')
llist.insertAtBeginning('brown')
llist.insertAtBeginning('quick')
llist.insertAtBeginning('the')
# Insert a word at the end
llist.insertAtEnd('jumps')
# Print the list
llist.printList()
请注意,我们只需调用 insertAtEnd 方法,即可在列表末尾加入单词“jumps”。上述代码应输出如下结果:
"the quick brown fox jumps"
从链表开头删除节点
删除链表的第一个节点很容易,只需将头指针指向第二个节点即可。这样,第一个节点将不再属于链表。为此,请在 LinkedList 类中加入以下方法:
def deleteFromBeginning(self):
if self.head is None:
return "The list is empty" # If the list is empty, return this string
self.head = self.head.next # Otherwise, remove the head by making the next node the new head
从链表末尾删除节点
要删除链表的最后一个节点,需要遍历链表找到倒数第二个节点,并将其 next 指针设为 None。这样,最后一个节点将不再属于链表。将以下方法复制到您的 LinkedList 类中即可完成:
def deleteFromEnd(self):
if self.head is None:
return "The list is empty"
if self.head.next is None:
self.head = None # If there's only one node, remove the head by making it None
return
temp = self.head
while temp.next.next: # Otherwise, go to the second-last node
temp = temp.next
temp.next = None # Remove the last node by setting the next pointer of the second-last node to None
该方法首先检查链表是否为空,若为空则向用户返回提示信息。否则,若链表只包含一个节点,就移除该节点。对于包含多个节点的情况,方法会定位到倒数第二个节点,并将其后继指针更新为 None。
现在我们来更新主函数,从链表的开头与末尾删除元素:
if __name__ == '__main__':
llist = LinkedList()
# Insert words at the beginning
llist.insertAtBeginning('fox')
llist.insertAtBeginning('brown')
llist.insertAtBeginning('quick')
llist.insertAtBeginning('the')
# Insert a word at the end
llist.insertAtEnd('jumps')
# Print the list before deletion
print("List before deletion:")
llist.printList()
# Deleting nodes from the beginning and end
llist.deleteFromBeginning()
llist.deleteFromEnd()
# Print the list after deletion
print("List after deletion:")
llist.printList()
上述代码会分别在删除前后打印链表内容,以演示链表的插入与删除操作。运行后您应看到如下输出:
List before deletion:
the quick brown fox jumps
List after deletion:
quick brown fox
在链表中搜索特定值
本章最后一个要学习的操作是从链表中检索特定值。为实现该功能,方法应从头节点开始,依次遍历每个节点,并检查节点数据是否与搜索值匹配。以下是该操作的实际实现:
def search(self, value):
current = self.head # Start with the head of the list
position = 0 # Counter to keep track of the position
while current: # Traverse the list
if current.data == value: # Compare the list's data to the search value
return f"Value '{value}' found at position {position}" # Print the value if a match is found
current = current.next
position += 1
return f"Value '{value}' not found in the list"
要在我们创建的链表中查找特定值,请更新主函数以包含刚刚实现的搜索方法:
if __name__ == '__main__':
llist = LinkedList()
# Insert words at the beginning
llist.insertAtBeginning('fox')
llist.insertAtBeginning('brown')
llist.insertAtBeginning('quick')
llist.insertAtBeginning('the')
# Insert a word at the end
llist.insertAtEnd('jumps')
# Print the list before deletion
print("List before deletion:")
llist.printList()
# Deleting nodes from beginning and end
llist.deleteFromBeginning()
llist.deleteFromEnd()
# Print the list after deletion
print("List after deletion:")
llist.printList()
# Search for 'quick' and 'lazy' in the list
print(llist.search('quick')) # Expected to find
print(llist.search('lazy')) # Expected not to find
上述代码将输出如下结果:
List before deletion:
the quick brown fox jumps
List after deletion:
quick brown fox
Value 'quick' found at position 0
Value 'lazy' not found in the list
由于单词“quick”位于链表的第一个位置,因此成功被找到。而“lazy”并不在链表中,因此未被找到。
结语
如果您读到了这里,恭喜您!您已经扎实掌握了链表的基本原理,包括其结构、类型、如何添加与删除元素,以及如何遍历。
不过,这段旅程并未就此结束。链表只是数据结构与算法世界的起点。以下是一些后续学习方向,帮助您进一步加深理解:
动手做一个项目
将链表应用到编程或数据科学项目中,探索其实际用途。链表可用于开发文件系统、构建哈希表,甚至用于创建 GPS 导航系统和棋盘游戏。要启动您的个人项目,请查看我们的免费引导式数据科学项目,学习如何用 Python、R 和 SQL 解决真实世界的问题。
学习数据结构与算法
在理解链表之后,树、栈和队列等其他数据结构是自然的进阶选择。这些结构建立在链表原理之上,可帮助您更高效地解决更广泛的计算问题。例如,树与二叉搜索树将链表的概念扩展为层级结构,使每个节点可以连接到数据结构中的多个元素。
如果这些概念对您来说还不熟悉,也不必担心!Datacamp 提供完整的 Python 数据结构与算法课程,将更为详尽地讲解这些内容。您将首先学习栈、树、哈希表、队列和图等数据结构。随着课程深入,您还将理解搜索与排序算法,助您成为更高效的程序员与问题解决者。
探索高级链表概念
本教程实现了单向链表,涵盖了插入、删除与遍历等操作。
您可以更进一步,学习双向链表与循环链表的实现。跳表(Skip list)是链表的另一种扩展,通过加速访问元素来实现更快的搜索操作。
掌握这些高级数据结构将使您的技术能力更上一层楼,显著提升编程水平,为您在数据科学、软件开发与机器学习工程等领域应对更复杂挑战做好准备。
如果您希望在进入这些高级主题之前先进行更友好的入门学习,不妨探索我们的Python Programmer 职业路径。它包含一系列课程,帮助您掌握该语言的基础。