【问题标题】:LinkedHashMap vs LinkedHashSet for retrieving specific elements & retrieving in insertion orderLinkedHashMap 与 LinkedHashSet 用于检索特定元素并按插入顺序检索
【发布时间】:2021-04-18 12:28:12
【问题描述】:

我正在做一个显然需要使用 Set 的问题,但我需要按插入顺序从集合中检索元素 + 检索特定元素。

但是找到特定元素太慢了(我猜是O(n),因为我必须遍历整个集合才能找到并返回它)。

所以我选择了LinkedHashMap<someClass,someClass>,其中键值映射包含相同的对象。

虽然这样更快,但它占用了两倍的内存,如果我的键/值(无论如何都相同)碰巧占用了大量空间,这尤其令人担忧。

我希望是否有人对我的问题或优化有更好的解决方案。

编辑: 顺便说一句,SO answer 的 cmets 可能会有所帮助

编辑:

 public Set<CompanyDummy> run(int numberOfCompanies) 
 {
        Set<CompanyDummy> companies=new LinkedHashSet<>();
        
        //Create companies
        CompanyDummy company;
        for(int idx=0;idx<=numberOfCompanies-1;idx++)
        {
            company=new CompanyDummy(idx);
            companies.add(company);
        }
        
        
        //Some code here to fill up each CompanyDummy element with engineers
        
        //At this point,there will be input 
        //specifying which companies to merge via their index(not reference)
        //Problem guarantees those companies exist. Hence, my reason why 
        //I didn't  do something like
        //if (set.contains(value)) return value;
        
        //Do note when we merge companies u & v, v is closed down
        
        for(int idx=0;idx<=transactions-1;idx++)
        {
            companyID= scanner.nextInt();
            anotherCompanyID= scanner.nextInt();
            
            //This part is where I search through companies to find if one exists
            //which is O(n)
            //Replacing sets with maps somehow makes the program faster
            //despite LinkedHashSet being backed by LinkedHashMap
            company=findCompany(companies, companyID);
            anotherCompany=findCompany(companies, anotherCompanyID);
            
            if(company!=null && anotherCompany!=null)
            {
                company.union(anotherCompany);
                companies.remove(anotherCompany);
            }
            

        }
 }
 
 private CompanyDummy findCompany(Set<CompanyDummy> companies,int id)
 {
        
        for(CompanyDummy company : companies)
        {
            if(company.getIndex()==id)
            {
                return company;
            }
        }
        
        return null;
  }

}


class CompanyDummy
{
private int index;
private Set<Integer> engineers;//The Integer here just denotes the engineer

public  CompanyDummy(int index) 
{
    this.index=index;
}

public int getindex()
{
    return index;
}

public void union(CompanyDummy someCompany)
{
    this.engineers.addAll(someCompany.engineers);
}

}

【问题讨论】:

  • 您能解释一下 LinkedHashSet 为何不适合您的使用吗?
  • 有一点,正如我所说,我必须检索特定元素。这部分导致我的程序对于大输入非常慢。

标签: java dictionary set time-complexity disjoint-sets


【解决方案1】:

好的,所以现在我看到了代码,我可以很好地理解问题并给你一个解决方案。

您不只是从集合中检索特定元素。如果它是集合的成员,则实际上是在特定字段中检索具有特定值的元素。这就是为什么您不能使用集合(或地图)的快速查找操作1

解决方案:

如果您想以比O(N) 更好的方式进行检索,则需要Map&lt;Integer, CompanyDummy&gt;。必须填充该映射,以便它从id 映射到CompanyDummy,并将id 作为其index。然后您可以将findCompany 调用替换为Map.get 调用。

请注意,将Set&lt;CompanyDummy&gt; 替换为Map&lt;CompanyDummy, CompanyDummy&gt; 将无助于解决此问题。两者都会给你O(N) 性能。


您是这样评价您的实验,将LinkedHashSet&lt;CompanyDummy&gt; 替换为LinkedHashMap&lt;CompanyDummy, CompanyDummy&gt;

虽然这样更快,但它占用了两倍的内存,如果我的键/值(无论如何都一样)碰巧占用了大量空间,这尤其令人担忧。

我怀疑这两件事是否真的是真的。

  1. 两个数据结构的空间利用率应该相同,因为LinkedHashSet 实际上是LinkedHashMap

  2. (IMO)不太可能在性能上存在显着的真正差异。当然不是2倍的差异。最可能的解释是,您测量和比较性能的方法确实适当考虑了由(各种)JVM 启动开销引起的可能的时序失真;例如类加载、堆大小调整、垃圾回收和 JIT 编译。


1 - 只是为了完整起见,如果不要求您的集合/地图按插入顺序排序,您可以使用 TreeSet&lt;CompanyDummy&gt; 和自定义 Comparator 来订购 CompanyDummy 对象他们的index 值。然后你可以使用OrderedSet.ceiling 和一个虚拟的CompanyDummy 实例来探测O(logN) 中的集合。

【讨论】:

  • “如果你想比O(N)更好地进行检索,你需要一个Map&lt;Integer, CompanyDummy&gt;....两者都会给你O(N)的性能。”。你的意思是说检索是O(1),但总体还是O(N)
  • 没有。我的意思是用Set&lt;CompanyDummy&gt;Map&lt;CompanyDummy, CompanyDummy&gt; 实现findCompany 的语义会导致O(N) 调用。您不能使用Set.containsMap.get ...这是性能优势所在。您需要通过id 快速查找而不是迭代。迭代是O(N)
  • 你的意思是Map.get()Map&lt;CompanyDummy, CompanyDummy&gt; 仍然是O(n) 还是只是用Maps 替换Sets 并且仍然使用findCompany 仍然是O(n)?你说的是后者吧?
  • A get 调用(比如说)LinkedHashMap&lt;CompanyDummy, CompanyDummy&gt; 将是 O(1)但是,将getMap&lt;CompanyDummy, CompanyDummy&gt; 一起使用将不适用于您的用例。你不能那样实现你的findCompany 方法。 get 调用需要正确的键作为参数。您还没有CompanyDummy 密钥。这就是您要查找的内容!所以你必须迭代,也就是O(N)
【解决方案2】:

在java中,HashSet是使用HashMap实现的。

HashSet 确实不适用于特定元素检索。映射中的键应该是标识映射到它的值对象的东西。此外,将复杂对象用作映射键通常不是一个好主意,除非您有一个非常好的 HashCode() 实现。

map 也不会维护插入顺序(LinkedHashMap 可能会保留它,因为它由 LinkedList 支持)。您可以将键设置为表示插入顺序的整数序列。例如:

Map<Integer, SomeClass> map = new HashMap<>();
map.put(0, object1);
map.put(1, object2);

等等。这将比 LinkedList 更好,因为获取时间是 O(1)。稍后,您可以在带有运行索引的 for 循环中获取它们,以按插入顺序获取它们。

如果您需要 HashSet 的属性来防止集合中的重复,您将需要保留每个对象的某个唯一标识符的 Set。例如一个人的 ID#。在将每个对象插入地图之前,您还要检查“唯一键”是否不在集合中,如果是,则将“唯一键”插入集合和地图。这一切都取决于您的对象的性质。

如果您需要其他任何东西,请在 cmets 中告诉我。祝你好运!

【讨论】:

    【解决方案3】:

    虽然这样更快,但它占用了两倍的内存,如果我的键/值(无论如何都相同)碰巧占用了大量空间,这尤其令人担忧。

    我不知道LinkedHashMap 会更快。考虑到LinkedHashSet 是使用支持LinkedHashMap 实例实现的,可能会快一点,因此与直接使用LinkedHashMap 相比,它会有一点开销。

    至于内存,无论键/值实例的大小如何,两者都将占用完全相同的内存量。您会看到,当您将元素 X 添加到 LinkedHashSet 时,它实际上将条目 &lt;X,PRESENT&gt; 放入底层 LinkedHashMap(其中 PRESENT 是对某个虚拟 Object 的引用)。将&lt;X,X&gt; 替换为&lt;X,PRESENT&gt; 没有区别,因为LinkedHashMap 只包含对键和值的引用,而不是键/值实例的副本。

    但是找到特定元素太慢了(我猜是 O(n),因为我必须遍历整个集合才能找到它)

    HashSet/LinkedHashSet 使用 O(1) 来确定特定元素是否已经在 Set 中。您不必遍历整个 Set。如果Set 包含该元素,则您已经拥有对它的引用。您不必寻找与您正在搜索的元素相同的 Set 元素。

    【讨论】:

    • 对于最后一段,我做了一个编辑,我其实也想把它返回,但既然你说它使用了 LinkedHashMap internally,我猜它是O(1)?出于某种原因,Set 我的程序大约需要 1 分钟,Map 需要 3 秒
    • @Leon 在这种情况下,请提供 2 个短代码 sn-ps 来重现您的问题(一个使用 LinkedHashMap,另一个使用 LinkedHashSet)..
    • @Leon - 根据您所描述的行为,我怀疑您没有使用 Set.contains 来检查集合成员资格。 containsO(1)LinkedHashSet 上。
    • @StephenC 是的,我没有使用包含,因为我也想返回它。问题指定它将是一个有效的元素
    • 嗯?我不跟着你。 if (set.contains(value)) return value; 似乎是这样做的。也许您需要向我们展示相关代码,以便我们了解您的问题。请参阅上面 Eran 的评论。
    猜你喜欢
    • 1970-01-01
    • 2013-11-01
    • 1970-01-01
    • 2020-02-24
    • 2012-07-05
    • 1970-01-01
    • 2019-03-29
    • 2011-02-12
    • 1970-01-01
    相关资源
    最近更新 更多