我使用了很多列表和数组,但是我还没有遇到这样一种情况:数组列表不能像链接列表一样容易地使用。我希望有人能给我一些有关链表何时明显更好的例子。
我使用了很多列表和数组,但是我还没有遇到这样一种情况:数组列表不能像链接列表一样容易地使用。我希望有人能给我一些有关链表何时明显更好的例子。
Answers:
在以下情况下,链表优于数组:
您需要从列表中进行固定时间的插入/删除操作(例如,在实时计算中,时间可预测性至关重要)
您不知道列表中有多少个项目。对于数组,如果数组太大,可能需要重新声明并复制内存
您不需要随机访问任何元素
您希望能够在列表中间插入项目(例如优先级队列)
在以下情况下最好使用数组:
您需要对元素进行索引/随机访问
您可以提前知道数组中元素的数量,以便为数组分配正确的内存量
依次遍历所有元素时需要速度。您可以在数组上使用指针数学来访问每个元素,而您需要基于指针为链接列表中的每个元素查找节点,这可能会导致页面错误并可能导致性能下降。
内存是一个问题。填充的数组占用的内存少于链接列表。数组中的每个元素只是数据。每个链接列表节点都需要数据以及指向链接列表中其他元素的一个(或多个)指针。
数组列表(如.Net中的数组列表)为您提供了数组的好处,但可以为您动态分配资源,因此您不必担心列表大小,您可以轻松删除任何索引的项目,而无需付出任何努力或重新改组周围的元素。在性能方面,数组列表要比原始数组慢。
数组具有O(1)随机访问权,但向其中添加内容或从中删除内容确实非常昂贵。
链表在任何地方添加或删除项以及进行迭代都非常便宜,但是随机访问为O(n)。
Algorithm ArrayList LinkedList
seek front O(1) O(1)
seek back O(1) O(1)
seek to index O(1) O(N)
insert at front O(N) O(1)
insert at back O(1) O(1)
insert after an item O(N) O(1)
ArrayLists对于一次写入多次读取或追加程序很有用,但不利于从前端或中间进行添加/删除。
为了增加其他答案,大多数数组列表实现在列表末尾保留了额外的容量,以便可以在O(1)时间将新元素添加到列表末尾。当超出数组列表的容量时,将在内部分配一个更大的新数组,并复制所有旧元素。通常,新数组的大小是旧数组的两倍。这意味着,在这些实现中,平均而言,将新元素添加到数组列表的末尾是O(1)操作。因此,即使您不预先知道元素的数量,只要您在末尾添加元素,数组列表可能仍比链接列表快得多。显然,在数组列表中的任意位置插入新元素仍然是O(n)操作。
即使访问是顺序的,访问数组列表中的元素也比链接列表要快。这是因为数组元素存储在连续的内存中,并且可以轻松地进行缓存。链接列表节点可能会分散在许多不同的页面上。
如果您知道要在任意位置插入或删除项目,则建议仅使用链接列表。数组列表几乎可以处理其他所有内容。
如果您需要在中间插入项目,而又不想开始调整数组的大小并四处移动,则可以看到列表的优点。
您是正确的,通常情况并非如此。我有一些这样的非常具体的案例,但是没有太多。
这完全取决于您在迭代时执行的操作类型,所有数据结构都在时间和内存之间进行权衡,并且根据我们的需求,我们应该选择正确的DS。因此,在某些情况下,LinkedList比array更快,反之亦然。考虑数据结构的三个基本操作。
由于数组是基于索引的数据结构,搜索array.get(index)将花费O(1)时间,而链表不是索引DS,因此您需要遍历index,其中index <= n,n是链表的大小,因此,当元素具有随机访问权限时,数组会更快地链接列表。
问:那么这背后的美丽是什么?
由于数组是连续的内存块,因此它们的大块将在首次访问时加载到高速缓存中,这使得访问数组的其余元素相对较快,因为我们在引用数组中访问元素的位置也增加了,因此捕获较少未命中,高速缓存局部性是指操作在高速缓存中,因此与在内存中的执行相比,执行起来要快得多。基本上,在数组中,我们最大化了顺序元素访问在高速缓存中的机会。尽管链接列表不一定位于连续的内存块中,但不能保证列表中顺序出现的项实际上在内存中彼此排列得很近,这意味着更少的缓存命中率,例如
在LinkedList中这很容易并且快速,因为与数组相比,在LinkedList中插入O(1)操作(在Java中)(考虑数组的情况),如果数组已满,则需要将内容复制到新数组中,这使得插入在最坏的情况下,将元素插入O(n)的ArrayList中,而如果在数组末尾插入任何内容,则ArrayList还需要更新其索引,如果是链表,我们不需要调整其大小,则只需更新指针。
它像插入一样工作,并且在LinkedList中比数组更好。
这些是Collection的最常用实现。
数组列表:
通常在末尾插入/删除O(1)最坏情况O(n)
在中间O(n)中插入/删除
检索任何位置O(1)
链表:
在O(1)的任何位置插入/删除(请注意是否引用了该元素)
在中间O(n)中检索
检索第一个或最后一个元素O(1)
向量:请勿使用。这是一个与ArrayList类似的旧实现,但是所有方法都已同步。对于多线程环境中的共享列表,这不是正确的方法。
哈希图
通过O(1)中的键插入/删除/检索
TreeSet 插入/删除/包含O(log N)
HashSet 在O(1)中插入/删除/包含/大小
1)如上所述,与ArrayList(O(n))相比,插入和删除操作在LinkedList中具有良好的性能(O(1))。因此,如果需要在应用程序中频繁添加和删除,那么LinkedList是最佳选择。
2)搜索(获取方法)操作在Arraylist(O(1))中很快,但在LinkedList(O(n))中却不快,因此,如果添加和删除操作较少,而对搜索操作的需求更多,则ArrayList将是您最好的选择。
我做了一些基准测试,发现对于随机插入,列表类实际上比LinkedList快:
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int count = 20000;
Random rand = new Random(12345);
Stopwatch watch = Stopwatch.StartNew();
LinkedList<int> ll = new LinkedList<int>();
ll.AddLast(0);
for (int i = 1; i < count; i++)
{
ll.AddBefore(ll.Find(rand.Next(i)),i);
}
Console.WriteLine("LinkedList/Random Add: {0}ms", watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
List<int> list = new List<int>();
list.Add(0);
for (int i = 1; i < count; i++)
{
list.Insert(list.IndexOf(rand.Next(i)), i);
}
Console.WriteLine("List/Random Add: {0}ms", watch.ElapsedMilliseconds);
Console.ReadLine();
}
}
}
链接列表需要900毫秒,列表类需要100毫秒。
它创建后续整数的列表。每个新整数都插入列表中已经存在的随机数之后。也许List类使用的不仅仅是数组。
可以使用以下几点来简单地回答该问题:
当需要收集相似类型的数据元素时,将使用数组。而链接列表是混合类型数据链接元素(称为节点)的集合。
在数组中,可以在O(1)时间内访问任何元素。而在链表中,我们需要从头到需要的节点遍历整个链表,所需时间为O(n)。
对于数组,最初需要声明特定大小。但是链接列表的大小是动态的。