Insertion in the middle of ArrayList vs LinkedList
在Java的上下文中交谈。如果我想插入一个ArrayList或一个linkedList的中间,我被告知ArrayList会表现得很糟糕。
我理解这是因为,我们需要移动所有元素,然后进行插入。这应该是N/2的顺序,即O(N)。
但对于linkedList来说,情况不同吗?对于链表,我们需要遍历到找到中间位置,然后进行指针操作。在这种情况下,也需要O(N)时间。不是吗?
谢谢
- 可能更适合程序员stackexchange
- 任意插入对于ArrayList和LinkedList都是O(n)(对于平均和最坏的性能)。然后问题归结为哪个系数更大。分析并找出原因。
- @达多-这里很好…依我所见
- 我不是说不能在这里回答,只是说它可能会引起程序员更多的注意。
这里的原因是链接列表中的元素没有实际的移动。链接列表是由节点组成的,每个节点都包含一个元素和一个指向下一个节点的指针。要将元素插入列表,只需要以下几点:
创建一个新节点来保存元素;
将前一个节点的下一个指针设置为新节点;
将新节点的下一个指针设置为列表中的下一个元素。
如果你曾经做过一系列回形针,你可以把每一个回形针看作是回形针链的开始,以及其后的所有回形针。要将新的回形针插入链条,只需在新回形针将要插入的位置断开回形针,然后插入新回形针。LinkedList就像一个回形针链。
ArrayList类似于一个柱箱或一个Mancala板,每个隔间只能容纳一个物品。如果你想在中间插入一个新的元素(并且保持所有元素的顺序相同),你就必须将所有元素都移到该位置之后。
在链表中给定节点之后的插入是固定时间,只要您已经对该节点引用(在Java中使用EDCOX1×2),到达该位置通常需要在节点的位置上线性时间。也就是说,要到达第n个节点,需要采取n个步骤。在数组列表(或数组,或任何基于连续内存的结构)中,列表中第个元素的地址只是(第一个元素的地址)+n×;(元素的大小),一个小的算术位,我们的计算设备支持对任意内存地址的快速访问。
- 而ArrayList试图总是有大约10个空元素来处理扩展,因此通过插入中间,它将数组克隆到元素重新排序之上的另一个数组。
- @Richardtingle就是我要说的,它必须对元素重新排序,并且在使用ArrayList的时候克隆它。因此,对于这个"简单"的任务,会发生更多的操作。
- @JoshuaTaylor但是为了这个你需要遍历列表直到插入点。是shifting过程代价高昂吗?如果是,你能告诉我为什么是这样吗?否则,即使LinkedList也会采取n/2步骤到达列表中间进行插入。
- @Kraken你忘了每次你添加元素到一个ArrayList,它必须创建一个全新的ArrayList,并克隆所有元素。它通过迭代内部化的数组来实现这一点。因此,这比简单地更改两个引用(比如使用某种类型的链接列表)要复杂得多。
- @克雷肯啊,是的,继续用回形针做类比;如果你还没有把第n个回形针放在链子里,它需要采取步骤才能找到它。但是,如果您已经用一个ListIterator来保存它,那么实际插入的时间是恒定的。
- LinkedList中的任意插入是o(n),就像ArrayList中的插入一样。对于LinkedList,边迭代边插入速度更快。
- @snakedoc但是arraylist有一些额外的空间用于相同的目的。所以尽管我们有可能达到满负荷,但我所说的另一种情况是,当我们有足够的空间添加而不重新分配时
- @Kraken你不能决定何时ArrayList重新分配它的内部化数组…它只是自动发生的。我可能是错的,但我认为它总是争取有10个自由元素,这意味着如果你添加一个元素,它可能会或可能不会重新分配。所以基本上你必须假设它会在每次添加时重新分配。
- @Tedhop这是一种常见的描述方法,但我真的希望更多的教员/教科书等能够描述实际的复合操作。给定一个链接列表中的节点,添加一个新节点作为其下一个节点的时间是恒定的。检索给定节点是O(N)。给定一个数组,将元素写入数组中的某个位置是恒定的时间。在数组中调整n个元素是O(N),这样就可以打开一个特定的位置进行写入。
- @snakedoc您可以预先分配数组中的空间,并决定何时这样做(使用ensureCapacity)。另外,我正在研究的JDK在需要重新分配时将由1.5 times增长。
- 遍历到LinkedList中的节点与移动以打开ArrayList中的槽的相对性能完全不清楚。LinkedList中的节点可以广泛分散在内存中,遍历到一个节点可能涉及多个缓存未命中,而ArrayList具有引用位置的优势。另外,由于ArrayList通常具有额外的容量,因此并不总是需要重新分配内部数组。在这两种情况下,都应该研究插入物的摊余成本。实际性能取决于系统负载、LinkedList在内存中的分布方式等。
- @泰德霍普,我同意你所说的一切。我的观点很简单,因为LinkedList支持基于ListIterator的固定时间插入,因此ArrayList没有对应的插入。如果已知将使用LinkedList,则可以出于性能原因利用它。重复"在链表中插入是O(N)"并不意味着,对于链表的许多用途,插入实际上是一个固定时间的操作。
- @泰德霍普,这实际上是在最近的一个答案中,一个操作想要交错两个列表(长度m和n)。仅使用list.add的解决方案是o(m n),但使用listirator.add的版本(只要列表是LinkedList)是o(m+n)。简单地说,在链接列表中插入需要O(N)不会留下空间,因为您可以使用m+n操作而不是m n操作在长度为n的列表中插入m元素。
- 我同意在许多操作中,LinkedList比ArrayList有明显的优势。但是,合并列表的场景(包括在遍历过程中插入)与OP的问题(即"中间插入")无关。
我认为,在分析复杂性时,您需要考虑到您使用的度量标准。在ArrayList中,您的度量是shuffling,这只是赋值。但这是一个相当复杂的操作。
另一方面,您使用的是LinkedList,您只需查看参考。实际上,您只执行1次插入。因此,虽然算法的复杂性最终会相似,但在O(n)时间执行的实际进程是不同的。在ArrayList的情况下,它执行了大量的内存操作。在一个LinkedList的例子中,它只是一个读数。
对于那些说他不了解LinkedList的人
LinkedList只在开始处有一个指针,在结束处有一个指针。它不会自动知道要删除的节点后面的节点(除非它是双链接列表),所以您需要遍历该列表,从创建临时指针开始,直到到达要删除的节点之前的节点,我相信OP正在讨论这个问题。