【问题标题】:Time complexity of TreeMap operations- subMap, headMap, tailMapTreeMap 操作的时间复杂度——subMap、headMap、tailMap
【发布时间】:2012-12-26 18:40:38
【问题描述】:

有谁知道 TreeMap 的操作时间复杂度,比如 subMap、headMap。尾图。

get、put 等操作的时间复杂度为 O(logn)。 但是javadoc并没有对上述操作的复杂性说太多。

我能想到的最坏情况复杂度是 O(n),因为如果集合包含最后一个元素,它将遍历整个列表。 我们可以确认一下吗?

【问题讨论】:

  • 无论哪种方式,简单查看 TreeMap 的来源就会发现它们是 O(1) 操作。文档甚至暗示了这一点,指出子地图使用原始地图。

标签: java list treemap


【解决方案1】:

对于那些手头有源代码的问题非常有用,因为有了足够的 IDE 支持,您可以简单地浏览实现。查看TreeMap的源码可以看出,这三个方法都使用constructor of AscendingSubMap构造了一个新的地图:

public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
                                K toKey,   boolean toInclusive) {
    return new AscendingSubMap(this,
                               false, fromKey, fromInclusive,
                               false, toKey,   toInclusive);
}

除了通过超级构造函数将参数传递给NavigableSubMap类之外什么都不做:

super(m, fromStart, lo, loInclusive, toEnd, hi, hiInclusive);

所以这三个方法都是基于下面的构造函数:

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;
}

出于类型和断言检查的原因,我在这里看到的只是对compare 的调用。因此,它应该是 O(1)

您始终可以browse the source code online,但我真的建议您获取源文件并将它们链接到您选择的 IDE。

【讨论】:

  • 获取它们是 O(1),迭代它们需要更多。迭代器调用 successor() N 次,其中 N 是范围之间的元素数。我会说 O(N)。
【解决方案2】:

我可以浏览TreeMap的源代码以获取详细的实现。

如果您详细了解源代码,了解他们实际上是如何获取 subMap 的,它就像这样......

如果你看到 NavigableSubMap 的 size 方法

  public int size() {
        return (fromStart && toEnd) ? m.size() : entrySet().size();
    }

多次调用最终调用getCeilingEntry()函数中的entrySet()实现

final Entry<K,V> getCeilingEntry(K key) {
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = compare(key, p.key);
        if (cmp < 0) {
            if (p.left != null)
                p = p.left;
            else
                return p;
        } else if (cmp > 0) {
            if (p.right != null) {
                p = p.right;
            } else {
                Entry<K,V> parent = p.parent;
                Entry<K,V> ch = p;
                while (parent != null && ch == parent.right) {
                    ch = parent;
                    parent = parent.parent;
                }
                return parent;
            }
        } else
            return p;
    }
    return null;
}

所以我想从创建的子地图中获取实际地图;时间复杂度大于O(1)。

【讨论】:

  • 方法subMapheadMaptailMap在哪里调用NavigableSubMap.size()
  • 是的,他们没有,但他们确实在随后的调用中调用 getceilingentry(),如 size() 方法。我只是以 size() 为例。
  • 这三个方法只调用构造函数导致NavigableSubMap。他们在哪里调用GetCeilingEntry()?请参阅line 826,它导致第 1677 行,然后最终导致第 1235 行。
  • 好吧,让我这样解释。当您执行此 treesetobj.subMap(args) 时,它会创建一个 navigableSubMap 对象(例如 navobj)。但是当您执行 navobj.keySet() 时,它会调用 getCeilingEntry() 函数。所以它就像在调用构造函数时不会生成实际的子图。只有在调用 keySet() 和 size() 之类的操作时,它才会真正尝试获取子图。
猜你喜欢
  • 2016-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-27
  • 2012-03-17
  • 1970-01-01
  • 2020-01-23
相关资源
最近更新 更多