Python 中的链表:简介
目录
链表就像一个鲜为人知的列表表亲。它们不那么受欢迎或那么酷,你甚至可能不记得你的算法课上的它们。但在正确的背景下,它们真的可以发光。
在本文中,您将了解:
- 什么是链表以及何时应该使用它们
- 如何使用
collections.deque
您所有的链表需求 - 如何实现自己的链表
- 其他类型的链表是什么以及它们的用途
如果您想在求职面试中提高编码技能,或者除了常用的字典和列表之外,还想了解更多关于Python 数据结构的知识,那么您来对地方了!
了解链表
链表是对象的有序集合。那么是什么让它们与普通列表不同呢?链表与列表的不同之处在于它们在内存中存储元素的方式。列表使用连续的内存块来存储对其数据的引用,而链表将引用存储为它们自己元素的一部分。
主要概念
在更深入地了解什么是链表以及如何使用它们之前,您应该首先了解它们的结构。链表的每个元素称为一个节点,每个节点有两个不同的字段:
- 数据包含要存储在节点中的值。
- Next包含对列表中下一个节点的引用。
下面是一个典型的节点:
节点
链表是节点的集合。第一个节点称为head
,它用作遍历列表的任何迭代的起点。最后一个节点必须有它的next
引用指向None
以确定列表的结尾。这是它的外观:
链表
既然您已经了解了链表的结构,您就可以查看它的一些实际用例了。
实际应用
链表在现实世界中有多种用途。它们可用于实现(剧透警报!)队列或堆栈以及图形。它们对于更复杂的任务也很有用,例如操作系统应用程序的生命周期管理。
队列或堆栈
队列和堆栈仅在检索元素的方式上有所不同。对于队列,您使用先进/先出(FIFO) 方法。这意味着插入列表的第一个元素是第一个要检索的元素:
队列
在上面的图中,可以看到前面和后面的队列的元素。当您向队列中添加新元素时,它们将进入后端。当您检索元素时,它们将从队列的前面取出。
对于堆栈,您使用后进/前出(LIFO) 方法,这意味着插入列表中的最后一个元素是第一个被检索的:
堆
在上图中,您可以看到堆栈中插入的第一个元素(索引0
)在底部,而最后插入的元素在顶部。由于堆栈使用 LIFO 方法,插入的最后一个元素(在顶部)将首先被检索。
由于您从队列和堆栈的边缘插入和检索元素的方式,链表是实现这些数据结构的最方便的方法之一。您将在本文后面看到这些实现的示例。
图表
图形可用于显示对象之间的关系或表示不同类型的网络。例如,图形的可视化表示——比如有向无环图 (DAG)——可能如下所示:
有向无环图
有多种方法可以实现上述图形,但最常见的一种方法是使用邻接表。邻接表本质上是一个链表列表,其中图的每个顶点都与一组连接的顶点一起存储:
顶点 | 顶点链表 |
---|---|
1 | 2 → 3 → 无 |
2 | 4 → 无 |
3 | 没有任何 |
4 | 5 → 6 → 无 |
5 | 6 → 无 |
6 | 没有任何 |
在上表中,图形的每个顶点都列在左列中。右列包含一系列链表,其中存储了与左列中相应顶点连接的其他顶点。此邻接列表也可以使用以下代码在代码中表示dict
:
>>> graph = {
... 1: [2, 3, None],
... 2: [4, None],
... 3: [None],
... 4: [5, 6, None],
... 5: [6, None],
... 6: [None]
... }
这个字典的键是源顶点,每个键的值是一个列表。这个列表通常被实现为一个链表。
注意:在上面的示例中,您可以避免存储None
值,但为了与后面的示例保持清晰和一致性,我们将它们保留在此处。
在速度和内存方面,与例如邻接矩阵相比,使用邻接列表实现图非常有效。这就是链表对于图实现如此有用的原因。
性能比较:列表与链接列表
在大多数编程语言中,链表和数组在内存中的存储方式存在明显差异。然而,在 Python 中,列表是动态数组。这意味着列表和链表的内存使用非常相似。
进一步阅读: Python 的动态数组实现非常有趣,绝对值得一读。一定要看看并利用这些知识在您的下一次公司聚会上脱颖而出!
由于列表和链表之间的内存使用差异如此微不足道,因此在涉及时间复杂度时,您最好关注它们的性能差异。
元素的插入和删除
在 Python 中,您可以使用.insert()
或将元素插入到列表中.append()
。要从列表中删除元素,您可以使用它们的对应项:.remove()
和.pop()
。
这些方法之间的主要区别在于您使用.insert()
and.remove()
插入或删除列表中特定位置的元素,但您使用.append()
and.pop()
仅插入或删除列表末尾的元素。
现在,您需要了解的有关 Python 列表的知识是,插入或删除不在列表末尾的元素需要在后台进行一些元素移位,这使得操作在花费的时间方面更加复杂。你可以阅读上面提到的文章列表如何用Python实现更好地理解如何执行.insert()
,.remove()
,.append()
并.pop()
影响他们的表现。
考虑到所有这些,即使在列表末尾插入元素使用.append()
或.insert()
将有恒定的时间,O (1),当您尝试在列表的开头或附近插入元素时,平均时间复杂度会增加以及列表的大小:O ( n )。
另一方面,当涉及到在列表的开头或结尾插入和删除元素时,链表要简单得多,它们的时间复杂度总是恒定的:O (1)。
出于这个原因,链表在实现队列 (FIFO) 时比普通列表具有性能优势,其中元素在列表的开头连续插入和删除。但是在实现堆栈 (LIFO) 时,它们的性能类似于列表,其中元素在列表的末尾插入和删除。
检索元素
在元素查找方面,列表的性能比链表好得多。当您知道要访问哪个元素时,列表可以在O (1) 时间内执行此操作。尝试对链表执行相同操作需要O ( n ),因为您需要遍历整个列表才能找到元素。
然而,在搜索特定元素时,列表和链表的表现非常相似,时间复杂度为O ( n )。在这两种情况下,您都需要遍历整个列表以找到您要查找的元素。
介绍 collections.deque
在 Python 中,collections
模块中有一个特定对象可用于链表,称为deque(发音为“deck”),它代表双端队列。
collections.deque
使用链表的实现,您可以在其中访问、插入或删除列表开头或结尾的元素,性能恒定为O (1)。
如何使用 collections.deque
默认情况下,有很多方法带有deque
对象。但是,在本文中,您将只涉及其中的几个,主要用于添加或删除元素。
首先,您需要创建一个链表。您可以使用以下代码段来执行此操作deque
:
>>> from collections import deque
>>> deque()
deque([])
上面的代码将创建一个空的链表。如果你想在创建时填充它,那么你可以给它一个可迭代的作为输入:
>>> deque(['a','b','c'])
deque(['a', 'b', 'c'])
>>> deque('abc')
deque(['a', 'b', 'c'])
>>> deque([{'data': 'a'}, {'data': 'b'}])
deque([{'data': 'a'}, {'data': 'b'}])
初始化deque
对象时,您可以将任何可迭代对象作为输入传递,例如字符串(也是可迭代对象)或对象列表。
既然您知道如何创建deque
对象,您就可以通过添加或删除元素与它进行交互。您可以创建一个abcde
链表并添加一个新元素,f
如下所示:
>>> llist = deque("abcde")
>>> llist
deque(['a', 'b', 'c', 'd', 'e'])
>>> llist.append("f")
>>> llist
deque(['a', 'b', 'c', 'd', 'e', 'f'])
>>> llist.pop()
'f'
>>> llist
deque(['a', 'b', 'c', 'd', 'e'])
双方append()
并pop()
从链接列表的右侧添加或删除元素。但是,您还可以使用deque
从列表的左侧或 快速添加或删除元素head
:
>>> llist.appendleft("z")
>>> llist
deque(['z', 'a', 'b', 'c', 'd', 'e'])
>>> llist.popleft()
'z'
>>> llist
deque(['a', 'b', 'c', 'd', 'e'])
使用deque
对象从列表的两端添加或删除元素非常简单。现在您已准备好学习如何使用collections.deque
来实现队列或堆栈。
如何实现队列和堆栈
正如您在上面了解到的,队列和堆栈之间的主要区别在于您从每个队列中检索元素的方式。接下来,您将了解如何使用collections.deque
来实现这两种数据结构。
队列
对于队列,您希望将值添加到列表 ( enqueue
),并且当时机合适时,您希望删除在列表中存在时间最长的元素 ( dequeue
)。例如,想象一下在一家时尚且订满的餐厅排队。如果您试图为客人实施公平的座位系统,那么您首先要创建一个队列并在他们到达时添加人员:
>>> from collections import deque
>>> queue = deque()
>>> queue
deque([])
>>> queue.append("Mary")
>>> queue.append("John")
>>> queue.append("Susan")
>>> queue
deque(['Mary', 'John', 'Susan'])
现在,队列中有 Mary、John 和 Susan。请记住,由于队列是先进先出的,第一个进入队列的人应该最先出去。
现在想象一下,随着时间的流逝,有几张桌子可用。在此阶段,您希望以正确的顺序从队列中删除人员。这是你将如何做到这一点:
>>> queue.popleft()
'Mary'
>>> queue
deque(['John', 'Susan'])
>>> queue.popleft()
'John'
>>> queue
deque(['Susan'])
每次调用 时popleft()
,都会从链表中删除头元素,模拟现实生活中的队列。
堆栈
如果你想创建一个堆栈呢?嗯,这个想法或多或少与队列相同。唯一的区别是堆栈使用 LIFO 方法,这意味着要插入堆栈的最后一个元素应该是第一个被删除的元素。
想象一下,您正在创建一个 Web 浏览器的历史记录功能,其中存储用户访问的每个页面,以便他们可以轻松地回到过去。假设这些是随机用户在浏览器上执行的操作:
- 访问Real Python 的网站
- 导航到Pandas:如何读写文件
- 单击用于在 Python 中读取和写入 CSV 文件的链接
如果您想将此行为映射到堆栈中,则可以执行以下操作:
>>> from collections import deque
>>> history = deque()
>>> history.appendleft("https://realpython.com/")
>>> history.appendleft("https://realpython.com/pandas-read-write-files/")
>>> history.appendleft("https://realpython.com/python-csv/")
>>> history
deque(['https://realpython.com/python-csv/',
'https://realpython.com/pandas-read-write-files/',
'https://realpython.com/'])
在此示例中,您创建了一个空history
对象,并且每次用户访问新站点时,您都使用 将其添加到history
变量中appendleft()
。这样做可以确保每个新元素都添加到head
链表的 中。
现在假设用户阅读完这两篇文章后,他们想返回 Real Python 主页选择一篇新文章阅读。知道您有一个堆栈并希望使用 LIFO 删除元素,您可以执行以下操作:
>>> history.popleft()
'https://realpython.com/python-csv/'
>>> history.popleft()
'https://realpython.com/pandas-read-write-files/'
>>> history
deque(['https://realpython.com/'])
你去吧!使用popleft()
,您从head
链表的 中删除元素,直到您到达 Real Python 主页。
从上面的示例中,您可以看到它collections.deque
在您的工具箱中是多么有用,因此请确保在下次您需要解决基于队列或堆栈的挑战时使用它。
实现你自己的链表
既然您知道如何使用collections.deque
处理链表,您可能想知道为什么要在 Python 中实现自己的链表。这样做有几个原因:
- 练习你的 Python 算法技能
- 学习数据结构理论
- 准备工作面试
如果您对以上任何内容都不感兴趣,或者您已经在 Python 中实现了自己的链表,请随意跳过下一节。否则,是时候实现一些链表了!
如何创建链接列表
首先,创建一个类来表示您的链表:
class LinkedList:
def __init__(self):
self.head = None
您需要为链表存储的唯一信息是列表的开始位置(head
列表的位置)。接下来,创建另一个类来表示链表的每个节点:
class Node:
def __init__(self, data):
self.data = data
self.next = None
在上面的类定义中,您可以看到每个节点的两个主要元素:data
和next
。您还可以__repr__
向两个类添加,以获得更有用的对象表示:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def __repr__(self):
return self.data
class LinkedList:
def __init__(self):
self.head = None
def __repr__(self):
node = self.head
nodes = []
while node is not None:
nodes.append(node.data)
node = node.next
nodes.append("None")
return " -> ".join(nodes)
看一下使用上述类快速创建具有三个节点的链表的示例:
>>> llist = LinkedList()
>>> llist
None
>>> first_node = Node("a")
>>> llist.head = first_node
>>> llist
a -> None
>>> second_node = Node("b")
>>> third_node = Node("c")
>>> first_node.next = second_node
>>> second_node.next = third_node
>>> llist
a -> b -> c -> None
通过定义节点的data
和next
值,您可以非常快速地创建链表。这些LinkedList
和Node
类是我们实现的起点。从现在开始,一切都是为了增加它们的功能。
下面是对链表的一个小改动,__init__()
它允许您使用一些数据快速创建链表:
def __init__(self, nodes=None):
self.head = None
if nodes is not None:
node = Node(data=nodes.pop(0))
self.head = node
for elem in nodes:
node.next = Node(data=elem)
node = node.next
通过上述修改,创建用于以下示例的链表将更快。
如何遍历链表
您将使用链表做的最常见的事情之一是遍历它。遍历意味着遍历每个节点,从head
链表的开始,到next
值为的节点结束None
。
遍历只是一种更高级的迭代方式。因此,考虑到这一点,创建一个__iter__
以将相同的行为添加到链表中,这与您对普通列表的期望相同:
def __iter__(self):
node = self.head
while node is not None:
yield node
node = node.next
上面的方法遍历列表并产生每个节点。对此要记住的最重要的事情__iter__
是,您需要始终验证当前node
不是None
。当该条件为 时True
,表示您已到达链接列表的末尾。
在产生当前节点后,您想移动到列表中的下一个节点。这就是为什么你添加node = node.next
. 这是遍历随机列表并打印每个节点的示例:
>>> llist = LinkedList(["a", "b", "c", "d", "e"])
>>> llist
a -> b -> c -> d -> e -> None
>>> for node in llist:
... print(node)
a
b
c
d
e
在其他文章中,您可能会看到将遍历定义为名为 的特定方法traverse()
。但是,使用 Python 的内置方法来实现上述行为会使这个链表实现更加Pythonic。
如何插入新节点
有多种方法可以将新节点插入链表,每种方法都有自己的实现和复杂程度。这就是为什么你会看到它们被分成特定的方法,用于在列表的开头、结尾或节点之间插入。
在开头插入
在列表的开头插入一个新节点可能是最直接的插入,因为您不必遍历整个列表来执行此操作。这一切都是关于创建一个新节点,然后head
将列表的 指向它。
看看add_first()
类的以下实现LinkedList
:
def add_first(self, node):
node.next = self.head
self.head = node
在上面的示例中,您设置self.head
为next
新节点的引用,以便新节点指向旧self.head
. 之后,您需要声明head
列表的新节点是插入的节点。
以下是示例列表的行为方式:
>>> llist = LinkedList()
>>> llist
None
>>> llist.add_first(Node("b"))
>>> llist
b -> None
>>> llist.add_first(Node("a"))
>>> llist
a -> b -> None
如您所见,add_first()
始终将节点添加到head
列表的 ,即使列表之前为空。
最后插入
在链表末尾插入一个新节点会强制您首先遍历整个链表,并在到达末尾时添加新节点。您不能像使用普通列表那样直接附加到末尾,因为在链表中您不知道哪个节点是最后一个。
下面是将节点插入到链表末尾的函数的示例实现:
def add_last(self, node):
if self.head is None:
self.head = node
return
for current_node in self:
pass
current_node.next = node
首先,您要遍历整个列表,直到到达末尾(即,直到for
循环引发StopIteration
异常)。接下来,您要将 设置current_node
为列表中的最后一个节点。最后,您想添加新节点作为next
that的值current_node
。
下面是一个add_last()
在行动的例子:
>>> llist = LinkedList(["a", "b", "c", "d"])
>>> llist
a -> b -> c -> d -> None
>>> llist.add_last(Node("e"))
>>> llist
a -> b -> c -> d -> e -> None
>>> llist.add_last(Node("f"))
>>> llist
a -> b -> c -> d -> e -> f -> None
在上面的代码,你用四个值创建列表开始(a
,b
,c
和d
)。然后,当您使用 添加新节点时add_last()
,您可以看到这些节点始终附加到列表的末尾。
在两个节点之间插入
在两个节点之间插入为链表已经很复杂的插入增加了另一层复杂性,因为您可以使用两种不同的方法:
- 在现有节点之后插入
- 在现有节点之前插入
将它们分成两种方法似乎很奇怪,但是链接列表的行为与普通列表不同,并且您需要针对每种情况使用不同的实现。
这是在具有特定数据值的现有节点之后添加节点的方法:
def add_after(self, target_node_data, new_node):
if self.head is None:
raise Exception("List is empty")
for node in self:
if node.data == target_node_data:
new_node.next = node.next
node.next = new_node
return
raise Exception("Node with data '%s' not found" % target_node_data)
在上面的代码中,您正在遍历链表以查找带有数据的节点,该数据指示要在何处插入新节点。当您找到要查找的节点时,您将立即在它之后插入新节点并重新连接next
引用以保持列表的一致性。
唯一的例外是如果列表为空,从而无法在现有节点之后插入新节点,或者列表不包含您要搜索的值。以下是一些add_after()
行为方式的示例:
>>> llist = LinkedList()
>>> llist.add_after("a", Node("b"))
Exception: List is empty
>>> llist = LinkedList(["a", "b", "c", "d"])
>>> llist
a -> b -> c -> d -> None
>>> llist.add_after("c", Node("cc"))
>>> llist
a -> b -> c -> cc -> d -> None
>>> llist.add_after("f", Node("g"))
Exception: Node with data 'f' not found
尝试add_after()
在空列表上使用会导致异常。当您尝试在不存在的节点之后添加时,也会发生同样的情况。其他一切都按预期工作。
现在,如果你想实现add_before()
,那么它看起来像这样:
1def add_before(self, target_node_data, new_node):
2 if self.head is None:
3 raise Exception("List is empty")
4
5 if self.head.data == target_node_data:
6 return self.add_first(new_node)
7
8 prev_node = self.head
9 for node in self:
10 if node.data == target_node_data:
11 prev_node.next = new_node
12 new_node.next = node
13 return
14 prev_node = node
15
16 raise Exception("Node with data '%s' not found" % target_node_data)
在执行上述操作时,需要记住一些事项。首先,与 一样add_after()
,如果链表为空(第 2 行)或您要查找的节点不存在(第 16 行),您要确保引发异常。
其次,如果您尝试在列表的头部(第 5 行)之前添加一个新节点,那么您可以重用,add_first()
因为您插入的节点将是head
列表中的新节点。
最后,对于任何其他情况(第 9 行),您应该使用prev_node
变量跟踪最后检查的节点。然后,当您找到目标节点时,您可以使用该prev_node
变量重新连接next
值。
再一次,一个例子胜过千言万语:
>>> llist = LinkedList()
>>> llist.add_before("a", Node("a"))
Exception: List is empty
>>> llist = LinkedList(["b", "c"])
>>> llist
b -> c -> None
>>> llist.add_before("b", Node("a"))
>>> llist
a -> b -> c -> None
>>> llist.add_before("b", Node("aa"))
>>> llist.add_before("c", Node("bb"))
>>> llist
a -> aa -> b -> bb -> c -> None
>>> llist.add_before("n", Node("m"))
Exception: Node with data 'n' not found
使用add_before()
,您现在拥有了在列表中任意位置插入节点所需的所有方法。
如何删除节点
要从链表中删除节点,首先需要遍历链表,直到找到要删除的节点。找到目标后,您希望链接其上一个和下一个节点。这种重新链接是从列表中删除目标节点的原因。
这意味着您需要在遍历列表时跟踪前一个节点。看一个示例实现:
1def remove_node(self, target_node_data):
2 if self.head is None:
3 raise Exception("List is empty")
4
5 if self.head.data == target_node_data:
6 self.head = self.head.next
7 return
8
9 previous_node = self.head
10 for node in self:
11 if node.data == target_node_data:
12 previous_node.next = node.next
13 return
14 previous_node = node
15
16 raise Exception("Node with data '%s' not found" % target_node_data)
在上面的代码中,您首先检查您的列表是否为空(第 2 行)。如果是,那么您会引发异常。之后,您检查要删除的节点是否是head
列表的当前节点(第 5 行)。如果是,那么您希望列表中的下一个节点成为新的head
.
如果上述情况均未发生,则开始遍历列表以查找要删除的节点(第 10 行)。如果找到,则需要更新其前一个节点以指向其下一个节点,自动从列表中删除找到的节点。最后,如果您遍历整个列表而没有找到要删除的节点(第 16 行),则会引发异常。
注意在上面的代码中你是如何previous_node
用来跟踪上一个节点的。这样做可确保当您找到要删除的正确节点时,整个过程将更加直接。
这是使用列表的示例:
>>> llist = LinkedList()
>>> llist.remove_node("a")
Exception: List is empty
>>> llist = LinkedList(["a", "b", "c", "d", "e"])
>>> llist
a -> b -> c -> d -> e -> None
>>> llist.remove_node("a")
>>> llist
b -> c -> d -> e -> None
>>> llist.remove_node("e")
>>> llist
b -> c -> d -> None
>>> llist.remove_node("c")
>>> llist
b -> d -> None
>>> llist.remove_node("a")
Exception: Node with data 'a' not found
而已!您现在知道如何实现链表以及遍历、插入和删除节点的所有主要方法。如果您对所学的内容感到满意并且渴望获得更多,那么请随意选择以下挑战之一:
- 创建一个从特定位置检索元素的方法:
get(i)
甚至llist[i]
. - 创建一个方法来反转链表:
llist.reverse()
。 Queue()
用enqueue()
和dequeue()
方法创建一个继承本文链表的对象。
除了是很好的练习之外,自己做一些额外的挑战是吸收所有知识的有效方法。如果您想通过重用本文中的所有源代码来抢先一步,那么您可以从以下链接下载所需的一切:
使用高级链表
到目前为止,您一直在学习一种称为单向链表的特定类型的链表。但是有更多类型的链表可用于略有不同的目的。
如何使用双向链表
双链表与单链表的不同之处在于它们有两个引用:
- 该
previous
字段引用前一个节点。 - 该
next
字段引用下一个节点。
最终结果如下所示:
节点(双向链表)
如果您想实现上述内容,那么您可以对现有Node
类进行一些更改以包含一个previous
字段:
class Node:
def __init__(self, data):
self.data = data
self.next = None
self.previous = None
这种实现将允许您在两个方向上遍历列表,而不仅仅是使用next
. 你可以next
用来前进和previous
后退。
就结构而言,这是双向链表的样子:
双向链表
您之前了解到collections.deque
使用链表作为其数据结构的一部分。这是它使用的那种链表。使用双向链表,deque
能够以恒定的O (1) 性能从队列的两端插入或删除元素。
如何使用循环链表
循环链表是一种链表,其中最后一个节点指向head
链表的 ,而不是指向None
。这就是使它们循环的原因。循环链表有很多有趣的用例:
- 在多人游戏中轮到每个玩家
- 管理给定操作系统的应用程序生命周期
- 实现斐波那契堆
这是一个循环链表的样子:
循环链表
循环链表的优点之一是可以从任意节点开始遍历整个链表。由于最后一个节点指向head
链表的 ,因此需要确保到达起点时停止遍历。否则,您将陷入无限循环。
在实现上,循环链表与单链表非常相似。唯一的区别是可以在遍历列表时定义起点:
class CircularLinkedList:
def __init__(self):
self.head = None
def traverse(self, starting_point=None):
if starting_point is None:
starting_point = self.head
node = starting_point
while node is not None and (node.next != starting_point):
yield node
node = node.next
yield node
def print_list(self, starting_point=None):
nodes = []
for node in self.traverse(starting_point):
nodes.append(str(node))
print(" -> ".join(nodes))
现在遍历列表会收到一个额外的参数 ,starting_point
用于定义迭代过程的开始和(因为列表是循环的)结束。除此之外,大部分代码与我们LinkedList
班级中的代码相同。
最后一个例子结束,看看当你给它一些数据时,这种新类型的列表是如何表现的:
>>> circular_llist = CircularLinkedList()
>>> circular_llist.print_list()
None
>>> a = Node("a")
>>> b = Node("b")
>>> c = Node("c")
>>> d = Node("d")
>>> a.next = b
>>> b.next = c
>>> c.next = d
>>> d.next = a
>>> circular_llist.head = a
>>> circular_llist.print_list()
a -> b -> c -> d
>>> circular_llist.print_list(b)
b -> c -> d -> a
>>> circular_llist.print_list(d)
d -> a -> b -> c
你有它!你会注意到你不再有None
遍历列表的时间。那是因为循环列表没有特定的结尾。您还可以看到,选择不同的起始节点将呈现相同列表的略有不同的表示。
结论
在这篇文章中,你学到了很多东西!最重要的是:
- 什么是链表以及何时应该使用它们
- 如何使用
collections.deque
实现队列和栈 - 如何实现自己的链表和节点类,以及相关方法
- 其他类型的链表是什么以及它们的用途
如果您想了解有关链表的更多信息,请查看Vaidehi Joshi 的 Medium 帖子以获得一个很好的视觉解释。如果您对更深入的指南感兴趣,那么维基百科文章非常详尽。最后,如果您对 的当前实现背后的推理感到好奇collections.deque
,请查看Raymond Hettinger 的线程。
- 点赞
- 收藏
- 关注作者
评论(0)