何时使用链表而不是数组/数组列表?

25 浏览
0 Comments

何时使用链表而不是数组/数组列表?

我经常使用列表和数组,但我还没有遇到过一个情况,其中数组列表不能像链表那样轻松地使用,如果不是更容易的话。我希望有人能给我一些链表显著优于数组列表的例子。

0
0 Comments

链表和数组/数组列表(ArrayList)在不同情况下的性能表现是不同的。根据给出的算法复杂度表格,我们可以得出以下结论:

1. 在需要频繁在前端或中间插入或删除元素的情况下,链表的性能优于数组/数组列表。链表在前端和后端插入元素的时间复杂度都为O(1),而数组/数组列表在前端插入的时间复杂度为O(N),需要移动其他元素。

2. 对于只读或追加的场景,数组/数组列表的性能更好。在查找首尾元素以及根据索引查找元素时,数组/数组列表的时间复杂度都为O(1),而链表的时间复杂度为O(N)。

3. 需要注意的是,在链表中插入到指定位置的时间复杂度为O(N)。只有当你已经有了要插入位置后面元素的指针时,链表的插入操作才是O(1)。否则,你需要遍历链表直到找到正确的位置,这将是O(N)的时间复杂度。

4. 对于数组/数组列表的末尾插入操作,只有当有可用的索引位置时,时间复杂度为O(1)。如果没有可用位置,数组/数组列表将需要调整大小,并将现有元素复制到新的数组中,这将是O(N)的时间复杂度。

根据上述分析,我们可以得出结论:在需要频繁插入或删除元素的情况下,链表是更好的选择,而在只读或追加操作较多的情况下,数组/数组列表更适合。需要注意的是,在链表中插入到指定位置的操作是比较耗时的,而数组/数组列表在末尾插入时也可能需要调整大小,导致性能下降。因此,在选择数据结构时,需要根据具体的需求和操作频率来权衡性能和效率。

示例代码如下:

// 使用链表实现插入操作
LinkedList linkedList = new LinkedList<>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(4);
// 在元素2之后插入元素3
ListIterator iterator = linkedList.listIterator();
while (iterator.hasNext()) {
    if (iterator.next() == 2) {
        iterator.add(3);
        break;
    }
}
System.out.println(linkedList); // 输出:[1, 2, 3, 4]
// 使用数组列表实现追加操作
ArrayList arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(4);
System.out.println(arrayList); // 输出:[1, 2, 4]

本文通过比较链表和数组/数组列表在不同操作下的性能表现,分析了它们的优劣势,并给出了相应的解决方法。在实际应用中,根据具体的需求和操作频率选择合适的数据结构非常重要,以提高程序的性能和效率。

0
0 Comments

什么时候使用链表而不是数组/数组列表?

数组具有O(1)的随机访问,但在添加或删除元素时非常昂贵。

链表非常便宜,可以在任何位置添加或删除项目,并进行迭代,但随机访问的时间复杂度为O(n)。

从数组末尾删除项是常数时间,从链表的任一端插入/删除项也是如此。但在中间位置,两者都不是这样。

那么在链表末尾插入/删除元素的时间复杂度是O(n)吗?除非您已经定位在倒数第二个链接,否则仍然需要O(n)的步骤才能找到最后一个元素,对吗?

-Niemi:对于单向链表是这样的。但是,许多链表有前向和后向的链接,因此保持对两端的指针。

使用双向链表将导致您在前向和后向进行搜索,除非您的链表具有有序值...即使如此,最坏情况下的时间复杂度仍为O(n)。

“链表非常便宜,可以在任何位置添加或删除项目并进行迭代”并不完全正确。如果我想从链表的中间删除一个项,我将不得不从开头迭代直到达到列表中的该项。这是O(n/2)的时间,其中n是列表中的项数。从您的回答中,您似乎在暗示它是常数时间O(1),就像在数组中一样。在链表的头部/根节点添加/删除是常数时间。

实际上,链表的迭代相当昂贵。在内存中跟随所有这些指针完全破坏了缓存局部性。

0
0 Comments

当何时使用链表而不是数组/数组列表?

链表比数组更适用于以下情况:

1. 当需要对链表进行常数时间的插入/删除操作时,比如在实时计算中,时间的可预测性非常重要。

2. 当不知道链表中会有多少项时。使用数组时,如果数组变得太大,可能需要重新声明和复制内存。

3. 当不需要对任何元素进行随机访问时。

4. 当希望在链表的中间插入项时,比如优先队列的情况。

数组在以下情况下更适用:

1. 当需要对元素进行索引/随机访问时。

2. 当事先知道数组中的元素数量,以便为数组分配正确数量的内存。

3. 当需要以顺序迭代所有元素时需要速度。可以使用指针运算在数组上访问每个元素,而链表需要根据指针查找每个元素的节点,这可能导致页面错误,从而影响性能。

4. 当内存是一个问题时。填充的数组比链表占用更少的内存。数组中的每个元素只是数据,而链表节点除了数据还需要一个或多个指向链表中其他元素的指针。

数组列表(比如.Net中的数组列表)提供了数组的优势,但会为您动态分配资源,因此您不需要太过关注列表的大小,可以在任何索引上删除项,而不需要额外的工作或重新排列元素。就性能而言,数组列表比原始数组慢。

链表支持结构共享,数组更密集且具有更好的局部性。

实际上,数组列表和数组之间的性能差异微不足道。这假设您进行可比较的比较,并且例如,当您事先知道大小时,您会告诉数组列表。

LinkedList是否具有O(1)的插入/删除(当您说“常数时间插入/删除”时,我想您指的是这个)?将东西插入LinkedList的中间始终是O(n)。

如果您已经位于插入位置(通过迭代器),则LinkedList的插入具有O(1)的时间复杂度。不过,并非总是如此。

使用链表来实现优先队列是一个非常愚蠢的想法。基于动态数组的堆允许O(lg n)的摊销插入和最坏情况下的对数删除最小,是最快的实际优先队列结构之一。

你能给我举个关于常数时间插入/删除的实际例子吗?我理解得不太好。

想象一个只关心“当前”项和相邻项的遍历算法,您需要能够在当前项旁边插入一个新项,或者您需要从这个窄窗口中删除一个项,那么您不需要数组提供的随机访问。想象一下数组就像一个具有整数键的字典,而链表就像磁带在磁带头上运行。在过去,音频编辑器可以在给定点剪辑/删除磁带的一部分。在这个点附近进行剪辑/删除是有意义的。

我能想到的一个现实世界的例子是按姓氏排序的电话簿。如果您需要添加新条目、编辑旧条目或删除旧条目,可能最合适的做法是按照电话簿的顺序执行所有这些任务,因此您将遍历每张卡片并根据需要插入/编辑/删除。

阅读了我的教授的代码后,我想我可以在您的解释中看到一些原因。在链表中,您不一定存储对象或指向它的指针,而是有一堆存储彼此之间和对应对象的指针的节点对象。例如,如果我有三个Person对象 - Tom,Dick和Harry,并且我想要一个链表,我将创建一个存储Tom(或指向他的指针)的节点,以及指向另一个节点的指针,该节点存储Dick(或指向他的指针),以及指向另一个节点的指针,该节点存储Harry(或指向他的指针)。

那听起来像是一个双端队列?

在双向链表的情况下,您可以使用对中间元素的引用创建自定义实现,并根据插入/删除调整引用的位置。这将实现在中间位置的插入的O(1)时间复杂度。

0