![](/img/trans.png)
[英]How is removeEldestEntry implemented and what is the runtime/complexity of the method in LinkedHashMap?
[英]LinkedHashMap complexity
我有一个简单的问题来找到数组A中的第一个唯一元素。但是,令我困扰的是使用不同方法的时间复杂性。 到目前为止,我已经尝试了这两种方法。
第一种方法:
LinkedHashMap<Integer, List<Integer>> map = new LinkedHashMap<Integer, List<Integer>>();
for (int i = 0; i < A.length; i++)
{
if (!map.containsKey(A[i]))
map.put(A[i], new ArrayList<>());
map.get(A[i]).add(i);
}
for (Map.Entry<Integer, List<Integer>> m : map.entrySet())
if (m.getValue().size() == 1)
return m.getKey();
return -1;
第二种方法:
for(int i=0; i< A.length; i++){
boolean unique = true;
nestedFor:for(int j=0; j< A.length; j++){
if(i != j && A[i] == A[j]){
unique = false;
break nestedFor;
}
}
if(unique)
return A[i];
}
return -1;
使用1000000个元素的数组进行测试,第一种方法的执行时间约为2000ms,而第二种方法的执行时间约为10ms。 我的问题是:与复杂度为O(n ^ 2)的第二种方法相比,它的复杂度为O(nLogn),因此第一种方法的执行速度是否应该更快? 我在这里想念什么? 测试代码下方:
int[] n = new int[1000000];
for (int i = 0; i < n.length; i++)
n[i] = new Random().nextInt(2000000);
long start = System.currentTimeMillis();
firstUnique(n);
System.err.println("Finished at: " + (System.currentTimeMillis() - start ) + "ms");
编辑:
for (int i = 0; i < A.length; i++)
{
if (!map.containsKey(A[i]))
map.put(A[i], new ArrayList<>());
map.get(A[i]).add(i);
}
消耗99%的执行时间,而
for (Map.Entry<Integer, List<Integer>> m : map.entrySet())
if (m.getValue().size() == 1)
return m.getKey();
始终为1-3ms。 因此,是的,填满地图是最昂贵的操作。
您如何建议作为解决此类问题的最有效方法?
我怀疑您没有选择为第二种情况创建“最坏情况”条件的输入。
例如,如果构造数组使所有百万个元素都重复(例如A[i] = 2 * i / A.length
),则第二种方法比第一种方法慢,因为它具有检查元素的10^12
组合。
通过更改内部for循环的条件以仅从j = i + 1
检查,可以使其速度更快(大约快一倍),但是10^12 / 2
仍然是一个很大的数字。
如果您只是选择随机数来填充数组,则第一个元素很有可能是唯一的,而第一个和第二个元素中的一个很有可能是唯一的,依此类推。经过几个元素,您将达到几乎可以肯定该元素是唯一的,因此它将在几次迭代后停止。
第一种方法花费的2秒时间太长。 我只能认为您没有在基准测试之前正确地预热JIT。 但是,即使不尝试这样做,您的第一种方法对我来说也只需要40-50毫秒(经过几次迭代后降至10-15毫秒)。
大部分时间将归因于对象的创建-在键和值的自动装箱以及ArrayList
实例的创建中。
时间复杂度忽略了效率系数,因为通常知道函数随着输入大小的增加如何增长会更有用。 尽管第一个函数的时间复杂度较低,但是在较小的输入大小下,它会运行得慢得多,因为您要制作许多ArrayList
对象,这在计算上是昂贵的。 但是,第二种方法仅使用数组访问,这比实例化对象要便宜得多。
时间复杂度应从其渐近意义上理解(即,随着输入大小增长到googolplex),仅此而已。 如果算法具有线性时间复杂度,则仅意味着存在一些a,b,使得执行时间(大约!!)= a * inputsize + b。 它没有说出a和b的实际大小,并且两个线性算法仍可能存在巨大的性能差异,因为它们的a / b的大小差异很大。
(此外,您的示例是一个糟糕的示例,因为算法的时间复杂度应考虑所有基础操作(例如对象创建等)的复杂性。其他人的答案也暗示了这一点。)
考虑改用2套:
public int returnFirstUnqiue(int[] a)
{
final LinkedHashSet<Integer> uniqueValues = new LinkedHashSet<Integer>(a.length);
final HashSet<Integer> dupValues = new HashSet<Integer>(a.length);
for (int i : a)
{
final Integer obj = i;
if (!dupValues.contains(obj))
{
if (!uniqueValues.add(obj))
{
uniqueValues.remove(obj);
dupValues.add(obj);
}
}
}
if (!uniqueValues.isEmpty())
{
return uniqueValues.iterator().next();
}
return -1;
}
首先是为什么基准不相关:
至于找到一个好的算法-您可以使用Map<Integer, Boolean>
代替Map<Integer, List<Integer>
因为你只需要存储的独特标志,而不是一个值的列表-添加与True
当你看到元素第一次,遇到双重性时切换为False
put
, containsKey
/ get
具有大的O复杂度O(n)(最坏的情况),使得整个算法为O(n ^ 2) put
的摊销复杂度为O(1)(使所有插入的摊销复杂度为O(n)),而get
平均复杂度是恒定的(这取决于所使用的哈希函数对给定输入的工作程度); 唯一值查找为O(n) 我的观察:第二种方法要快得多,因为它使用的是声明宽度的Array
。 在第一个示例中,大小发生了变化。
请尝试定义更精确的LinkedHashMap
大小,以将初始容量设置为1000000。
接下来的事情是Array是简单得多的结构,其中GC不尝试执行任何操作。 但是当涉及到LinkedHashMap
时,它比在Array
从特定索引处简单获取元素要复杂得多,并且在某些情况下创建和操作的成本要复杂得多。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.