在JavaDoc中写到,TreeSet的基本操作在log(N)时间中起作用,其中N是集合的大小。在我看来,如果set足够大,则headSet和tailSet方法应该通过诸如二进制搜索之类的方法找到它们正在计算的视图的起点,但是JavaDoc对此保持沉默。
答案 0 :(得分:4)
文档对headSet
和tailSet
的时间复杂性一无所知。他们只说:
返回此集合中元素严格小于toElement的部分的视图。返回的集合受此集合支持,因此返回的集合中的更改会反映在该集合中,反之亦然。返回的集合支持该集合支持的所有可选集合操作。
(我是强调 view )。而 view 确实是最重要的部分。在最坏的情况下,创建视图始终是O(1)
操作,因为仅创建包装器类。不执行键搜索,仅执行类型检查,实际上也不会触发其他任何操作。
这里的TreeSet.headSet(E toElement)
代码:
public SortedSet<E> headSet(E toElement) {
return headSet(toElement, false);
}
这是TreeSet.headSet(E toElement, boolean inclusive)
代码:
public NavigableSet<E> headSet(E toElement, boolean inclusive) {
return new TreeSet<>(m.headMap(toElement, inclusive));
}
您可能知道,TreeSet
由TreeMap
实例支持。这就是m
属性的实际含义。因此,上面的操作委托了TreeMap.headMap(E toElement, boolean inclusive)
方法,然后创建了一个新的TreeSet
实例,该实例由NavigableMap
返回的TreeMap.headMap(E toElement, boolean inclusive)
实例支持。
首先,让我们看一下TreeSet
构造函数:
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
如您所见,这只是一项任务。
然后,让我们深入研究TreeMap.headMap(E toElement, boolean inclusive)
方法的代码:
public NavigableMap<K,V> headMap(K toKey, boolean inclusive) {
return new AscendingSubMap<>(this,
true, null, true,
false, toKey, inclusive);
}
这仅创建并返回AscendingSubMap
静态嵌套类的实例。这是AscendingSubMap
构造函数的代码:
AscendingSubMap(TreeMap<K,V> m,
boolean fromStart, K lo, boolean loInclusive,
boolean toEnd, K hi, boolean hiInclusive) {
super(m, fromStart, lo, loInclusive, toEnd, hi, hiInclusive);
}
这只是委托给超类的构造函数(AscendingSubMap
的超类是NavigableSubMap
静态嵌套抽象类)。这是NavigableSubMap
构造函数的代码:
NavigableSubMap(TreeMap<K,V> m,
boolean fromStart, K lo, boolean loInclusive,
boolean toEnd, K hi, boolean hiInclusive) {
if (!fromStart && !toEnd) {
if (m.compare(lo, hi) > 0)
throw new IllegalArgumentException("fromKey > toKey");
} else {
if (!fromStart) // type check
m.compare(lo, lo);
if (!toEnd)
m.compare(hi, hi);
}
this.m = m;
this.fromStart = fromStart;
this.lo = lo;
this.loInclusive = loInclusive;
this.toEnd = toEnd;
this.hi = hi;
this.hiInclusive = hiInclusive;
}
如您所见,这只是检查参数的正确性并将其分配给属性。
这里m
是封闭的TreeMap
实例(请记住NavigableSubMap
是静态嵌套的抽象类)。 TreeMap.compare(Object k1, Object k2)
方法如下:
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
此方法仅比较其参数,此处仅用于检查子图的边界。 (请记住,TreeMap
键可以是Comparable
,也可以不是。如果不是,则在构造Comparator
实例时必须提供TreeMap
,这就是comparator
属性在上面的代码中。)
这就是调用headSet
方法时要做的所有事情。 tailSet
遵循相同的模式(只是最终子图的边界不同)。
因此,结论是headSet
和tailSet
实际上是O(1)
的最坏情况。
注意:我已经检查了JDK 8 v1.8.0_181
和openjdk version "11" 2018-09-25
两个版本的代码。我可以肯定中间版本也没有更改。
编辑:
关于访问headSet
返回的视图的第一个元素的时间复杂度,即,如果要对其进行迭代,则实现需要执行O(logN)
操作才能到达{ {1}}(毕竟,TreeSet
得到TreeSet
的支持,而TreeMap
被实现为一棵红/黑树)。
迭代由tailSet
返回的set视图具有相同的时间复杂度:O(logN)
。这是因为实现需要对值更接近提供的下限的节点执行搜索,并且此搜索也是O(logN)
。