为什么有人要在数组上使用链表?
毫无疑问,对链接列表进行编码比使用数组要花费更多的工作,并且您可能会想知道什么可以证明需要付出额外的努力。
我认为在链表中插入新元素很简单,但这是数组中的一项主要工作。与使用数组存储在数组中相比,使用链表存储还有其他优势吗?
这个问题不是一个重复这个问题,因为在这个问题涉及的一般数据结构的另一个问题是专门关于特定Java类要求。
为什么有人要在数组上使用链表?
毫无疑问,对链接列表进行编码比使用数组要花费更多的工作,并且您可能会想知道什么可以证明需要付出额外的努力。
我认为在链表中插入新元素很简单,但这是数组中的一项主要工作。与使用数组存储在数组中相比,使用链表存储还有其他优势吗?
这个问题不是一个重复这个问题,因为在这个问题涉及的一般数据结构的另一个问题是专门关于特定Java类要求。
Answers:
另一个很好的理由是,链表很适合高效的多线程实现。这样做的原因是更改往往是局部的-仅影响在数据结构的局部插入和删除的一个或两个指针。因此,您可以有多个线程在同一个链表上工作。甚至可以使用CAS类型的操作创建无锁版本,并完全避免使用笨重的锁。
使用链接列表,迭代器还可以在进行修改时遍历列表。在修改不冲突的乐观情况下,迭代器可以继续进行而不会引起争用。
对于数组,任何修改数组大小的更改都可能需要锁定数组的很大一部分,而且实际上,很少在整个数组上都没有全局锁定的情况下完成此操作,因此修改变得停止了世界事务。
ConcurrentSkipListMap
或ConcurrentSkipListMap
不是列表,即使“目录”在他们的名字出现的地方。两者都需要将被排序且唯一的键。如果您需要一个List
数据结构(即允许以任意顺序重复元素的数据结构),那么这是不合适的,那么您就必须花大力气来制作一个数据结构,例如LinkedList
可同时更新的事物。如果您只需要并发队列或双端队列,是的,甚至还有现有的示例,但是并发List
...我不相信这是可能的。
Wikipedia很好地介绍了这些差异。
链表比数组有几个优点。元素可以无限期地插入到链表中,而数组最终将被填满或需要调整大小,这是一项昂贵的操作,如果内存碎片化,这甚至是不可能的。类似地,从中删除了许多元素的数组可能会浪费很多,或者需要变得更小。
另一方面,数组允许随机访问,而链表仅允许顺序访问元素。实际上,单链列表只能在一个方向上遍历。这使得链接列表不适用于需要通过其索引快速查找元素的应用程序,例如堆排序。由于引用和数据缓存的局部性,与许多机器上的链表相比,对阵列的顺序访问也更快。链接列表几乎不会从缓存中受益。
链接列表的另一个缺点是引用需要额外的存储空间,这通常使它们对于小数据项(例如字符或布尔值)的列表不切实际。这也可能很慢,并且由于使用天真的分配器浪费资源,因此无法为每个新元素分别分配内存,这通常是使用内存池解决的问题。
我将添加另一个-列表可以充当纯功能数据结构。
例如,您可以拥有完全不同的列表,共享同一结束部分
a = (1 2 3 4, ....)
b = (4 3 2 1 1 2 3 4 ...)
c = (3 4 ...)
即:
b = 4 -> 3 -> 2 -> 1 -> a
c = a.next.next
无需将指向的数据复制a
到b
和中c
。
这就是为什么它们在使用不可变变量的功能语言中如此受欢迎的原因- prepend
并且tail
在将数据视为不可变的情况下,操作很容易进行而不必复制原始数据-这是非常重要的功能。
除了更容易插入列表的中间之外,从链接列表的中间删除也比数组更容易。
但坦率地说,我从未使用过链表。每当我需要快速插入和删除时,我也需要快速查找,所以我去了HashSet或Dictionary。
合并两个链表(尤其是两个双链表)比合并两个数组(假定合并是破坏性的)要快得多。前者取O(1),后者取O(n)。
编辑:为澄清起见,我的意思是在此处以无序的方式“合并”,而不是在合并排序中。也许“连接”是一个更好的词。
对于ArrayList和LinkedList而言,一个广为人知的参数是调试时LinkedLists不舒服。维护开发人员花费在理解程序上的时间(例如,发现错误,增加错误),并且IMHO有时无法证明性能提高的纳秒级或企业应用程序中的内存消耗字节。有时(当然,这取决于应用程序的类型),最好浪费一些字节,但是拥有一个更易于维护或易于理解的应用程序。
例如,在Java环境中并使用Eclipse调试器,调试ArrayList将显示一个非常容易理解的结构:
arrayList ArrayList<String>
elementData Object[]
[0] Object "Foo"
[1] Object "Foo"
[2] Object "Foo"
[3] Object "Foo"
[4] Object "Foo"
...
另一方面,观看LinkedList的内容并查找特定对象成为Expand-The-Tree单击的噩梦,更不用说过滤掉LinkedList内部所需的认知开销:
linkedList LinkedList<String>
header LinkedList$Entry<E>
element E
next LinkedList$Entry<E>
element E "Foo"
next LinkedList$Entry<E>
element E "Foo"
next LinkedList$Entry<E>
element E "Foo"
next LinkedList$Entry<E>
previous LinkedList$Entry<E>
...
previous LinkedList$Entry<E>
previous LinkedList$Entry<E>
previous LinkedList$Entry<E>
首先,在C ++中,使用链表应该不会比数组麻烦得多。您可以将std :: list或boost指针列表用于链接列表。链表和数组的关键问题是指针所需的额外空间和可怕的随机访问。如果您要使用链表
对我来说就是这样
访问
存储
尺寸
插入/删除
两件事情:
毫无疑问,编码一个链表比使用数组要花费更多的工作,他想知道什么能证明需要更多的努力。
使用C ++时,切勿编写链表。只需使用STL。实施起来有多难,决不应成为选择一个数据结构而不选择另一个数据结构的理由,因为大多数数据结构已经在那里实现。
至于数组和链表之间的实际差异,对我来说最大的事情是您如何计划使用该结构。我将使用术语向量,因为这是C ++中可调整大小的数组的术语。
索引到链接列表很慢,因为必须遍历列表才能到达给定的索引,而向量在内存中是连续的,并且可以使用指针数学到达那里。
将链接附加到链接列表的末尾或开头很容易,因为您只需要更新一个链接即可,在矢量中您可能需要调整大小并复制内容。
从列表中删除项目很容易,因为您只需断开一对链接,然后将它们重新连接在一起即可。从向量中删除项目可能更快或更慢,具体取决于您是否关心订单。在要删除的项目上方交换最后一个项目的速度更快,而向下移动所有内容的速度较慢,但会保持顺序。
对于链表,快速插入和删除确实是最好的参数。如果您的结构动态增长,并且不需要对任何元素(例如动态堆栈和队列)进行恒定时间访问,那么链表是一个不错的选择。
当集合不断增长和缩小时,链接列表特别有用。例如,很难想象要尝试使用数组实现Queue(添加到末尾,从前面移除)-您将花费所有时间将内容向下移动。另一方面,它与链接列表无关紧要。
除了从列表的中间添加和删除之外,我更喜欢链接列表,因为它们可以动态增长和收缩。
没有人再编码自己的链表了。那太傻了。使用链表需要更多代码的前提是错误的。
如今,建立链接列表只是学生的一项练习,因此他们可以理解概念。相反,每个人都使用一个预先构建的列表。在C ++中,基于我们问题的描述,这可能意味着一个stl向量(#include <vector>
)。
因此,选择一个链表和一个数组完全是权衡每种结构相对于应用程序需求的不同特征。克服额外的编程负担应该对决策产生零影响。
数组与链接列表:
假设您有一个有序集,您还想通过添加和删除元素来对其进行修改。此外,您需要能够以对后面的元素进行引用的方式保留对元素的引用,以便以后可以获取上一个或下一个元素。例如,一本书的待办事项列表或一组段落。
首先,我们应该注意,如果要保留对集合本身之外的对象的引用,则可能最终将指针存储在数组中,而不是存储对象本身。否则,您将无法插入到数组中-如果将对象嵌入到数组中,则它们将在插入过程中移动,并且指向它们的任何指针都将变为无效。数组索引也是如此。
您已经注意到自己的第一个问题是插入-链表允许插入O(1),但是数组通常需要O(n)。这个问题可以部分克服-可以创建一个数据结构,该结构提供类似于数组的常规访问接口,在这种情况下,读写都处于对数状态。
您的第二个也是更严重的问题是,给定一个元素,找到下一个元素是O(n)。如果未修改集合,则可以保留元素的索引作为引用而不是指针,从而使find-next成为O(1)操作,但实际上,您只有一个指向对象本身的指针,而没有办法而不是通过扫描整个“数组”来确定其当前在数组中的索引。对于数组来说,这是一个无法解决的问题-即使您可以优化插入操作,也无法执行优化find-next类型操作的操作。
链表
插入时更可取!基本上,它是处理指针的
1-> 3-> 4
插入(2)
1 ........ 3 ...... 4
..... 2
最后
1-> 2-> 3-> 4
2点中的一个箭头指向3,1点中的1箭头指向2
简单!
但是从数组
| 1 | 3 | 4 |
插入(2)| 1 | 3 | | 4 | | 1 | | 3 | 4 | | 1 | 2 | 3 | 4 |
好吧,任何人都可以看到差异!仅针对4个索引,我们执行3个步骤
如果阵列长度为一百万怎么办?数组效率高吗?答案是不!:)
删除同样的事情!在链接列表中,我们可以简单地使用指针,并在元素类中使元素和下一个元素无效!但是对于数组,我们需要执行shiftLeft()
希望有帮助!:)
链表维护比阵列维护更多的开销,同时所有这些要点都需要额外的存储空间。但是数组不能做一些事情。在许多情况下,假设您想要一个长度为10 ^ 9的数组,那么您将无法获得它,因为必须有一个连续的内存位置。链表可能是这里的救星。
假设您想用数据存储多个事物,那么可以在链接列表中轻松扩展它们。
STL容器通常在后台具有链接列表实现。
1-链接列表是一种动态数据结构,因此它可以在运行时通过分配和释放内存来增长和收缩。因此,无需给出链表的初始大小。节点的插入和删除确实非常容易。
2-链表的大小可以在运行时增加或减少,因此不会浪费内存。在数组的情况下,会浪费很多内存,例如,如果我们声明一个大小为10的数组并在其中仅存储6个元素,那么就会浪费4个元素的空间。链接列表中没有这种问题,因为仅在需要时才分配内存。
3-使用链接列表可以轻松实现数据结构,例如堆栈和队列。
我也认为链接列表比数组更好。因为我们在链接列表中遍历但不在数组中遍历
根据您的语言,可以考虑以下一些不利条件:
C编程语言:使用链接列表时(通常通过结构指针),必须特别注意以确保您不会泄漏内存。如前所述,由于所有操作都在更改指针,因此链接列表很容易洗牌,但是我们要记住释放所有内容吗?
Java:Java具有自动垃圾收集功能,因此不会出现内存泄漏的问题,但是高级程序员隐藏的是链接列表的实现细节。从列表的中间删除节点之类的方法比该语言的某些用户所期望的过程更复杂。
为什么要在数组上建立链表?就像某些人已经说过的那样,插入和删除的速度更快。
但是也许我们不必忍受任何一个的局限性,并同时获得两者的最大优势...是吗?
对于数组删除,您可以使用“已删除”字节来表示已删除一行的事实,因此不再需要重组数组。为了减轻插入或快速更改数据的负担,请使用链表。然后在提及它们时,让您的逻辑首先搜索一个,然后搜索另一个。因此,将它们组合使用可为您带来两者的最佳效果。
如果您有一个非常大的数组,则可以将其与另一个更小的数组或链表组合,其中较小的数组或链表包含20、50、100个最近使用的项。如果所需的列表不在较短的链表或数组中,请转到大型数组。如果在此找到,则可以将其添加到较小的链接列表/数组中,前提是“最近使用过的内容最有可能被重复使用”(是的,可能会从列表中删除最近使用最少的项目)。这在许多情况下是正确的,并且以轻松,优雅和令人印象深刻的速度解决了我必须在.ASP安全权限检查模块中解决的问题。
虽然你们中的许多人都谈到了链表与数组的主要关系/缺点,但大多数比较是一个比另一个好/坏的比较。您可以在数组中进行随机访问,但不能在链表和其他列表中进行访问。但是,这是假定链接列表和数组将在类似的应用程序中应用。但是,正确的答案应该是,在特定的应用程序部署中,如何优先选择链表而不是阵列,反之亦然。假设您要实现字典应用程序,您将使用什么?Array:mmm,可以通过二进制搜索和其他搜索算法轻松检索..但是让我们考虑一下如何使链接列表更好。.假设您要在字典中搜索“ Blob”。拥有A-> B-> C-> D ---->的链接列表是否有意义
A -> B -> C -> ...Z
| | |
| | [Cat, Cave]
| [Banana, Blob]
[Adam, Apple]
现在上述方法是更好的还是[Adam,Apple,Banana,Blob,Cat,Cave]的统一排列?数组甚至有可能吗?因此,链接列表的主要优点是您可以拥有一个不仅指向下一个元素的元素,而且还具有指向其他链接列表/数组/堆/或任何其他内存位置的元素。数组是一个平坦的连续内存,切成要存储的元素的块大小。另一方面,链接列表是一大块不连续的内存单元(可以是任意大小,可以存储任何内容),并指向每个其他您想要的方式。同样,假设您正在制作USB驱动器。现在,您是否希望将文件保存为任何数组或链接列表?我想您知道我指的是什么:)