【问题标题】:Optimal way to check for common element among many lists?在许多列表中检查共同元素的最佳方法?
【发布时间】:2016-12-11 12:41:50
【问题描述】:

我知道ArrayList<> 的搜索速度最快(O(1)O(n)),LinkedList<> 的插入和删除速度最快(O(1)O(n))。

我的问题是,如果将这两者结合使用,检查多个列表 (>2) 是否有共同元素的最佳方法是什么?

当前方法 使用三个列表和一个迭代方法:

out:
for(int a = 0; a < list1.size(); a++) {
    for(int b = 0; b < list2.size(); b++) {
        for(int c = 0; c < list3.size(); c++) {
            if(list1.get(a) == list2.get(b) && list1.get(a) == list3.get(c) ) {
                System.out.println(list1.get(a)); // list2.get(b) or list3.get(c) could have been subbed
                break out;
            }
        }
    } 
}

如何优化效率?


编辑

感谢您提供许多有用的回复 :) 我发现最好的方法是使用 List .retainAll() 函数。

再次,为了找到三个列表中的共同元素,我改进了下面的方法。

list1.retainAll(list2);
list1.retainAll(list3);
for(int i : list1) {
    System.out.println(i);
}

【问题讨论】:

  • 首先你访问同一个元素 3 次,将 list1.get(a) 存储在一个对象中,你将赢得 2 次列表访问
  • 我会使用地图并计算引用。如果你有 n 个列表并且你已经完成了,那么地图中所有计数为 n 的条目都描述了公共元素。这是假设每个列表的元素都是唯一的(没有重复条目)。
  • “我知道ArrayList&lt;&gt; 的搜索速度最快(O(1) vs. O(n))” 这完全取决于您的搜索方式。如果您使用迭代器,LinkedList 不是 O(n)。您正在考虑随机访问,但这是线性遍历。
  • 我会复制一份清单(例如,list1),然后使用listCopy.retainAll(list2); listCopy.retainAll(list3);listCopy 中留下的任何元素都对所有三个列表都是通用的。
  • 这样的琐碎代码不应该为了效率而优化,它应该为了清晰而优化。如果那时它成为性能热点,则可以重新评估。

标签: java algorithm performance optimization iteration


【解决方案1】:

假设元素实现hashCode,您可以获得与所有列表中元素数量呈线性关系的预期时间:

public static <T> Set<T> commonElements(List<? extends T> list1, List<? extends T>... lists) {
    // use LinkedList for efficient delete operation
    // make sure elements are distinct to not check the same element multiple times
    List<T> commonElements = new LinkedList<>(new HashSet<>(list1));
    for (List<? extends T> l : lists) {
        // use HashSet for fast contains check
        // keep only elements in the list
        commonElements.retainAll(new HashSet<>(l));
    }
    return new HashSet<>(commonElements);
}

这比您的方法更快,因为HashSet 允许在O(1) 预期时间进行查找。

请注意,尽管对于小型输入列表,这种方法的性能可能会更差。

【讨论】:

  • 为什么你认为LinkedList 在删除元素方面比HashSet 更有效?我认为将所有元素复制到 LinkedList 并将公共元素复制回 HashSet 的工作会掩盖任何细微的差异。
  • 我同意。这就是为什么你这样做new HashSet&lt;&gt;(list1)。但随后您将其复制到LinkedList,最后复制回HashSet。这意味着您已经为副本迭代了一次HashSet,这与您为第一个要排除的列表所做的迭代相同。您现在假设对于 3 路 commonElements,LinkedList 的两次迭代 + 复制回 HashSet 将比 HashSet 的第二次迭代更快。
【解决方案2】:

如果您正在寻找性能,最好编写一个使用哈希查找的 API。 list.retainAll(),虽然是一个干净的 api 调用,但在内部它会进行大量处理,特别是如果传递的参数也是一个列表。看看这里数组列表的retainAll()的实现——

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ArrayList.java#ArrayList.retainAll%28java.util.Collection%29

您可以查看您正在使用的列表的实现,看看这是否符合您的性能要求。如果没有,你可以试试这样的……写一个 api 来返回公共元素。

private static Set getCommonElements (List dataList, Set dataSet) {
    Set commonDataSet = new LinkedHashSet();

    if (dataSet == null || dataSet.isEmpty()) 
        return commonDataSet;

    for (Object elem: dataList) {
        if (dataSet.contains(elem)) {//Hash based look up. Will be faster.
            commonDataSet.add(elem);
        }
    }

    return commonDataSet;
}

然后重复调用如下

Set resultSet= new LinkedHashSet(list1);
resultSet= getCommonElements(list2, resultSet);
resultSet= getCommonElements(list3, resultSet);

如果您不关心顺序,您可以使用 hashset 而不是linkedhashset。

这样做的一个问题是,这是迭代列表中的元素,这将高于普通元素。如果我们可以遍历公共元素并在列表中查找会更好。但为此,您可能必须将列表中的数据保存在哈希烘焙列表/集中或维护排序列表。否则查找成本会很高。

【讨论】:

    【解决方案3】:

    您可以在 java 中使用 HashMap 对其进行优化。 假设你有 n 个列表,每个列表有 m 个元素

    算法:

      make hashmap h;
      loop i=0 to m
         loop j=0 to n
           increment j[i] key in hashmap h
         loop end
      loop end
    
    loop i=0 to m for any list
      check hashmap value for the element, if equals to n
      print element
    

    复杂度 o(nm),if n

    【讨论】:

      【解决方案4】:

      使用retainAll(List&lt;&gt;) 函数而不是对每个元素进行迭代可以显着减少运行时间并提高可读性。

      list1.retainAll(list2);
      list1.retainAll(list3);
      

      out:
      for(int a = 0; a < list1.size(); a++) {
          for(int b = 0; b < list2.size(); b++) {
              for(int c = 0; c < list3.size(); c++) {
                  if(list1.get(a) == list2.get(b) && list1.get(a) == list3.get(c) ) {
                      System.out.println(list1.get(a));
                      break out;
                  }
              }
          } 
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-06-05
        • 2015-10-28
        • 2019-04-09
        • 1970-01-01
        • 1970-01-01
        • 2019-07-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多