请解释链表在数组上的优势。与链表相比,使用数组还有什么优势。
此致 邻省
答案 0 :(得分:48)
两者都存储一系列元素,但使用不同的技术。
数组以内存中的连续顺序存储元素,即它如下所示:
--------------------------------------------------------------------------------------
| item 1 | item 2 | item 3 | ... ... | item x | //here comes other stuff
--------------------------------------------------------------------------------------
这意味着元素在内存中一个接一个连续。
另一方面,A((双重)链接)列表以下列方式存储项目:它为每个元素创建一个自己的“列表项”;这个“列表项”将实际元素和指针/引用/提示/等保存到下一个“列表项”。如果它是双向链接的,它还包含指向前一个“列表项”的指针/引用/提示/等:
------------
------------ ---------- | item 4 |
| item 1 | | item 2 | | next ---+----...
| next ---+------->| next | ------------
------------ ---+------ ^
| |
| |
v |
---------- |
| item 3 | |
| next --+---+
----------
这意味着,元素可以遍布整个内存,而不是存储在特定的内存位置。
现在我们知道这一点,我们可以比较元素序列的一些常规操作:
访问特定索引处的元素:使用数组,我们只需计算此索引的偏移量并拥有索引处的元素。
这很便宜。另一方面,使用列表,我们不知道存储索引元素的先验(因为所有元素都可以在内存中的任何位置),所以我们必须逐项遍历列表,直到我们在索引处找到元素。这是一项昂贵的操作。
在序列末尾添加新元素:使用数组,这可能会导致以下问题:数组通常具有固定大小,因此如果我们已经完全填充了我们的数组的情况(参见//here comes other stuff
),我们可能无法将新元素放在那里因为可能已经存在其他内容。所以,也许我们必须复制整个数组。但是,如果未填充数组,我们可以简单地将元素放在那里。
使用列表,我们必须生成一个新的“列表项”,将元素放入其中,并将当前最后一个元素的next
指针设置为此新列表项。通常,我们存储对当前最后一个元素的引用,这样我们就不必搜索整个列表来查找它。因此,插入新元素对列表来说不是问题。
在中间某处添加新元素:让我们首先考虑数组:在这里,我们可能会遇到以下情况:我们有一个包含元素的数组1到1000:
1 | 2 | 3 | 4 | 5 | 6 | ... | 999 | 1000 | free | free
现在,我们要在4.5
和4
之间插入5
:这意味着,我们必须将所有元素从5
移到1000
一个位置,以便为4.5
:
1 | 2 | 3 | 4 | free | 5 | 6 | ... | 999 | 1000 | free
||
vv
1 | 2 | 3 | 4 | 4.5 | 5 | 6 | ... | 999 | 1000 | free
移动所有这些元素是一项非常昂贵的操作。所以最好不要经常这样做。
现在我们考虑一下列表如何执行此任务:假设我们目前有以下列表:
1 -> 2 -> 3 -> 4 -> 5 -> ... -> 999 -> 1000
同样,我们希望在4.5
和4
之间插入5
。这可以很容易地完成:我们生成一个新的列表项并更新指针/引用:
1 -> 2 -> 3 -> 4 5 -> ... -> 999 -> 1000
| ^
+-> 4.5 -+
我们只是简单地创建了一个新的列表元素并生成了一种“旁路”来插入它 - 非常便宜(只要我们有一个指向列表项的指针/引用,之后就会插入新元素)。
所以,让我们总结一下:链接列表在随机位置插入时非常好(只要你有一个指向适当列表项的指针)。如果您的操作涉及动态添加大量元素并且遍历所有元素,那么列表可能是一个不错的选择。
数组在索引访问方面非常出色。如果您的应用程序需要经常访问特定位置的元素,则应该使用数组。
值得注意的事情:
解决数组的固定大小问题:如前所述,(原始)数组通常具有固定大小。然而,现在这个问题已不再是真正的问题,因为几乎所有编程语言都提供了生成可以动态增长(并可能缩小)的数组的机制 - 正如所需。可以实现这种增长和缩小,以便我们具有分摊的运行时O(1),用于插入和删除元素(在数组的末尾),并且程序员不必调用{明确{1}}和grow
。
由于列表为插入提供了很好的属性,因此它们可以用作搜索树等的底层数据结构。您构建一个搜索树,其最低级别由链表组成。
答案 1 :(得分:7)
数组具有固定的大小但访问速度更快:它们分配在一个地方,每个元素的位置都是已知的(您可以跳转到正确的元素)。
列表的大小不受限制,但可用内存量不受限制。它们访问速度较慢,因为要查找必须遍历列表的元素。
这是一个非常简短的解释:我建议你拿一本关于数据结构的书并阅读它。这些是您需要完全理解的基本概念。
答案 2 :(得分:5)
由于您使用“数据结构”标记了问题,我将在该上下文中回答这个问题。
当您声明/创建数组时,数组的大小是固定的,这意味着您无法向其中添加更多元素。因此,如果我有一个数组,比如5个元素,你可以用它做任何你想做的事情,但你不能添加更多的元素。
链接列表基本上是一种表示列表的方法,您可以在其中拥有任意数量的“项目”。它由列表中的头(第一个元素),尾部(最后一个元素)和元素(称为节点)组成。
您可能会在任何数据结构类中遇到许多类型的链接列表。
在学习如何在类中创建字段以指向其他对象时,您将学习链接列表的关键是熟练,这就是链接列表的情况,您需要构建列表,以便每个节点指向到下一个节点。
显然,这是一个非常普遍的答案。它应该为你的班级提供一个想法。
答案 3 :(得分:3)
数组具有存储在其中的每个元素的特定地址,因此我们可以直接访问任何内存。
我们知道中间元素和其他元素的位置也很容易访问,我们可以轻松地在数组中执行BINARY SEARCH。
需要提及的元素总数或在创建数组时需要完成内存分配
一旦提到,阵列的大小就无法在程序中增加。如果输入的元素数超过数组ARRAY OVERFLOW EXCEPTION的大小。
在程序开头不需要提及列表的大小。
由于链表没有大小限制,我们可以继续添加新节点(元素)并将列表大小增加到任何程度。
节点没有自己的地址。只存储第一个节点的地址,为了到达任何节点,我们需要遍历从开始到所需节点的整个列表。
由于所有节点都没有其特定地址,因此无法执行BINARY SEARCH。
答案 4 :(得分:0)
如果您不知道事先需要存储的对象数量,列表可能就是您想要的,因为根据需要动态缩小或增大列表非常容易。这样做的另一个好处是能够轻松地插入列表中的元素,而无需重新分配。
另一方面,列表相对于数组的缺点是选择单个元素的速度较慢,因为需要迭代。使用数组,您将不会遇到此问题。另一方面,如果需要调整数组大小,则使用数组很麻烦,因为此操作比添加或减少链表中的元素更昂贵。
列表应该更常用,因为使用易用性通常比使用静态大小阵列的小性能提升更有利。
答案 5 :(得分:0)
虽然有人提到数组的性能比链表更好,但我很惊讶地看到没有提及“#34; cache"任何地方。链表的问题主要在于它们几乎可以保证从节点到节点的每次跳转都是高速缓存未命中,这是难以置信,残酷昂贵,性能方面。
粗略地说,如果性能在程序中稍有影响,那么永远不会使用链接列表。永远。它没有任何借口。它们远远超出了“慢速”,它们是灾难性的慢,而它们没有任何东西可以弥补这一事实。使用它们就像是对你的表演的故意破坏,比如插入" wait()"完全没有理由在你的代码中运行。