至少有两种表示链表的方法:
1.)使用基于数组的链表的表示,其中
我们保留std::vector
类型
struct {
<whatever-type-you-want> item ;
int nextitem;
}
这里插入到列表中,正在向量上执行push_back()并给出 下一项的适当值。
2)你在哪
在RAM中有一组结构。这里插入完成
C ++运算符new
。
说第一种方法效率更高是正确的,因为所有项都在内存中的连续位置,因为其中一个方法可能会增长链表 比第二种方法更大的尺寸
在第二种方法中,可能存在大量链接列表的内存碎片,因为可能会更早地获得分段错误。
答案 0 :(得分:5)
我会反对其他所有人,并说,是的,第一种方法最终可能会更有效率。在第二种方法中,您在堆上分配内存O(N)次--N是列表中的节点数。如果您正在使用向量,那么您只需要进行O(log N)个堆分配。
此外,如果您使用的是64位计算机,如果您处理大量小项目,则在每个节点中保存指针的开销可能会有点过多。使用矢量,您可以使用较小的nextItem
- 例如32位而不是64位,如果你要制作一个容纳32位整数的列表,那么内存使用率将会提高1.5。
另一个可能的优化是,如果你事先知道你将处理很多元素,你可以保留一个大的向量,并在很长一段时间内进行单个堆分配。
我最近参加了自动机应用课程,讲师正在为大型数据集实现一些算法。他告诉我们的一种技术正是你表达链表的第一种方法。我有一个课程工作,我尝试实现两种方式(使用指针和矢量和nextItem
类的东西)和矢量一个表现得更好(它确实有其他优化,但矢量肯定有一个效果)。
注意其他人
我认为@smilingbuddha所询问的更像是链接列表的集合 - 或者至少是我用过它的东西。例如,使用邻居列表保存图形时。您需要每个节点的所有邻居的链表(或数组或其他)。因此,不是保留链表的数组或向量的向量,而是保持索引数组指向每个节点的最后插入的邻居。
答案 1 :(得分:3)
使用向量实现列表是错误的。
我会解释。容器通常设计用于实现某组目标,并且基于这些目标选择底层实现。
向量非常好,因为它具有连续的内存,您可以通过指针算法到达任何单元格。不幸的是,当向量中心插入或删除元素时,向量的性能很差。
列表具有完全相反的意图。导航到列表中的某个点非常耗时,因为您必须关注链接,因为它不是连续的。但是列表的主要目的是允许快速插入,删除,重新排序,拼接,反转等。
因此,将向量视为列表的实现基础(虽然可以完成)实际上并不是看待它的方法。使用向量实现列表基本上意味着您没有任何优势使您首先选择列表。
修改强>
正如其他人在下面的评论中指出的那样,如果您考虑的是更复杂的实现,那么您肯定可以获得性能优势。
例如,如果你维护一个引用所有指针的向量,并且你努力保持该引用向量的顺序,你可以获得指针算术访问的好处,同时仍然具有相对快速的删除/插入等此外,由于参考向量仅保存指向动态分配对象的指针,因此操作参考向量并不昂贵,并且您仍然不必使用大量连续内存区域(向量将只是NumElements * sizeof(你的架构上的指针)。
你应该看一下std :: deque实现的一些乐趣。它们在通过指针链接的连续内存区域之间有一些有趣的相互作用,以加速插入/删除/其他操作。
答案 2 :(得分:2)
恰恰相反;使用您的第一种方法,当您“丢失”存储该项目的向量中的插槽时,从链接列表中删除项目是低效的,并且必须以垃圾收集样式遍历整个列表以发现哪些插槽不是被使用。
关于内存碎片,有很多小分配通常不是问题;实际上,当向量需要连续分配内存时,导致碎片,因为您需要越来越大的连续内存块。此外,每次调整矢量大小时,都会导致复制大块内存。
事实上,你的第一个答案就是妄自尊重内存分配器和内存管理单元的工作。内存分配器的工作是分发小块内存; MMU(以及其他)的工作是确保即使在物理内存中移动时,内存块之间的指针仍然指向相同的逻辑内存。您的nextitem
int成员基本上是指针。除非你有非常专业的要求,否则硬件,内核和malloc可以比你做得更好。
答案 3 :(得分:1)
你的逻辑是完全倒退的。第一种方法要求内存是连续的,并且只要有足够的连续内存可用就会失败。你的第二种方法可以使用内存,无论是否连续,并将继续工作,直到根本没有内存。
答案 4 :(得分:0)
你的第一种方法似乎是混合了两种算法,因此,我认为效率较低。
链表的一个优点是可以轻松插入和删除项目。然而,使用您的方法,他们需要改变数据。你也可以使用一个简单的可调整大小的数组。
此外,数组要求内存是连续的。在某些情况下,在处理大量数据时,您将比使用真正的链表更早耗尽内存,因为有时可能会有一定数量的内存,但不是连续的。
答案 5 :(得分:0)
如果从案例#1中的列表中删除元素,则其余元素的很大一部分可能会使其nextitem
索引搞砸。所以#2是通常的方法,如果正确实现,不会导致任何内存问题,除非你试图在列表或任何其他容器中插入疯狂数量的元素。