【问题标题】:List vs. Map: Which takes less space and more efficient?列表与地图:哪个占用空间更少,效率更高?
【发布时间】:2014-05-03 18:12:01
【问题描述】:

我有两个班级FooBar

class Foo
{
    Set<Integer> bars; // Foo objects have collection of bars.
    Set<Integer> adjacents; // Adjacency list of Foos.
}

class Bar
{
    int foo; // ID of foo of which this object belongs to
    Ipsum ipsum; // This an arbitrary class. But it must be present
    Map<Integer, Float> adjacents; // Adjacency list of Bars
}

Bars 的数量是预定义的(最多 1000 个)。因此,我可以使用一个数组。 但是Foos 的数量是未定义的(最多#ofBars/4)。

当您考虑添加、删除和get() 时,我需要速度更快且占用空间更少的那个(因为我要使用序列化)。

这是我的选择(据我所知)

选项 1:不要为 Foo 定义类。相反,使用List&lt;Set&lt;Integer&gt;&gt; foo; 和另一个映射 Map> fooAdjacencies;
选项 2: 如果我想获得i 的条形图,请使用Map&lt;Integer, Set&lt;Integer&gt; foo,我只需写foo.get(i)选项 3: 不要定义类。相反,使用选项 2 和 Bar 类:

Map<Integer, Ipsum> bar;
Map<Integer, Map<Integer, Floar>> barAdjacencies;

在空间和时间效率方面我应该选择哪个选项?

【问题讨论】:

  • @LuiggiMendoza 感谢您的通知。我更正了。
  • 地图与列表根本不同。你需要什么行为?
  • 我需要我的结构在添加、删除和查找元素时高效。没有其他行为。
  • 参考。 ArrayList/List , HashMap/Map - 注意类文档中讨论的方法契约和边界。
  • 很难找出你在问什么,原因是你提出了一些预先优化的解决方案而不是问题。我建议写下你需要的方法的声明。我猜你的问题根本没有Integers。另请注意,序列化的表单可以与对象完全解耦,因此空间可能非常不相关。

标签: java performance list map set


【解决方案1】:

这听起来对你很有帮助(特别是数据结构部分):http://bigocheatsheet.com/

你说

我需要我的结构在添加、删除和查找元素时保持高效。没有其他行为。

问题是ListsMaps 通常用于完全不同的情况。他们的名字很好地描述了他们的用例——如果你需要列出一些东西(可能按顺序),你可以使用List,而如果你需要map 一个输入到一个输出。您可以通过将Integers 映射到您的元素,将Map 用作List,但这有点过于复杂了。但是,即使在 ListMap 内,您也可以有不同的实现,它们的渐近性能差异很大。

除了少数例外,数据结构将占用O(n) 空间,这是有道理的。如果内存服务,除了ArrayList(或仅由原始数组支持的其他集合)之外的任何东西都将具有相当大的空间开销,因为它们使用其他对象(例如Nodes 用于LinkedListsEntry 对象用于Maps) 来组织底层结构。不过,除非空间真的很宝贵,否则我不会太担心这种开销。

为了获得最佳性能的添加、删除和搜索,您需要查看数据结构是如何实现的。

  • LinkedList 样式的实现将让您获得O(1) 的添加和删除(并且还有一个很好的常数因子!),但是get()O(n) 的时间会非常昂贵,因为每次你想得到一些东西时都必须遍历列表。但是,Java 的 LinkedList 实现会在 O(n) 时间删除;虽然实际删除行为是O(1),但前提是您引用了要删除的实际节点。因为你不知道,Java 的 LinkedList 中的删除是 O(n) -- O(n) 用于搜索要删除的节点,O(1) 用于删除。

  • 由普通数组支持的数据结构将具有 O(1) get(),因为它是一个数组,但需要 O(n) 来添加和删除,因为除了最后一个元素之外的任何添加/删除都需要所有其他要洗牌的元素(至少在 Java 的实现中)。在O(n) 时间内使用对象而不是索引来搜索内容,因为您必须遍历数组才能找到对象。

以下两个结构通常是Maps,因此通常需要你实现equals()(和hashCode() for HashMaps):

  • 由树支持的数据结构(例如TreeMap)将摊销(我认为)O(lg n) 添加/删除,因为一个好的实现应该是自平衡的,只在最坏的情况下添加/删除最多必须经过树的高度。 get() 操作是 O(lg n)。使用树要求您的元素以某种方式可排序/可比较,这可能是一种奖励或阻碍,具体取决于您的使用情况。

  • 基于散列的数据结构已摊销(平均)O(1) 一切,尽管由于散列的开销而具有稍高的常数因子(如果散列传播很差,则遵循任何链)。 HashMaps 如果你写了一个糟糕的 hashCode() 函数,可能会开始很糟糕,所以你要小心,尽管 Java 的 HashMap 的实现者确实在幕后做了一些魔法,试图至少部分否定错误的hashCode() 实现的影响。

希望破败有所帮助。如果你弄清楚你的程序是如何结构的,我可能会给出一个建议。在那之前,我能做的最好的就是向您展示选项并让您选择。

【讨论】:

  • LinkedList 有 O(n) 次删除
  • 我想这取决于您是否已经有节点引用或者是否需要获取它。如果您已经引用了该节点,则为 O(1),否则从技术上讲,它是搜索 (O(n)),然后删除 (O(1))。我会澄清的。
【解决方案2】:

我觉得这个问题描述有点难以理解,但我认为您只是在寻找一般集合/数据结构的建议。

列表(例如,数组列表)允许您轻松地添加和迭代元素。当它扩展超出底层数组的大小时,将执行一次性代价高昂的调整大小操作以添加更多空间;但这很好,因为它很少发生,摊销时间也不错。在列表中搜索特定元素很慢,因为需要按顺序遍历;大多数列表中没有隐含的顺序。删除元素取决于底层列表实现。在这方面,数组列表可能会很慢;但我猜他们只是通过将底层元素标记为已删除并在迭代期间跳过它来优化它。使用列表时,您还必须考虑添加元素的位置。链表的迭代速度较慢,但​​可以轻松地在任何位置添加和删除元素。数组列表只能在末尾添加元素。

根据您的要求,如果您需要对元素执行“获取”或查找,那么您需要某种搜索功能来加快速度。这将使地图更好,因为您可以在 log(n) 时间内定位元素,而不是像搜索无序列表时那样的线性时间。在列表中添加和删除元素也相对较快,因此这可能是您的最佳选择。

最重要的是,以不止一种方式实现它并自己分析它以了解更多信息:) 但是,当需要搜索时,列表很少是一个好的选择。

【讨论】:

  • 我可能选错词了 :) 我的意思是get() 操作。
猜你喜欢
  • 2020-06-18
  • 2018-04-02
  • 1970-01-01
  • 2021-05-10
  • 2019-11-22
  • 1970-01-01
  • 2010-12-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多