为什么SortedSet <t> .GetViewBetween不是O(log N)?</t>

时间:2012-03-24 10:29:57

标签: c# .net complexity-theory sortedset

在.NET 4.0+中,类SortedSet<T>有一个名为GetViewBetween(l, r)的方法,它返回树部件上的接口视图,其中包含指定的两个值之间的所有值。鉴于SortedSet<T>被实现为红黑树,我自然希望它能在O(log N)时间内运行。 C ++中的类似方法是std::set::lower_bound/upper_bound,在Java中它是TreeSet.headSet/tailSet,它们是对数的。

然而,事实并非如此。以下代码在32秒内运行,而等效O(log N)版本的GetViewBetween将使此代码在1-2秒内运行。

var s = new SortedSet<int>();
int n = 100000;
var rand = new Random(1000000007);
int sum = 0;
for (int i = 0; i < n; ++i) {
    s.Add(rand.Next());
    if (rand.Next() % 2 == 0) {
        int l = rand.Next(int.MaxValue / 2 - 10);
        int r = l + rand.Next(int.MaxValue / 2 - 10);
        var t = s.GetViewBetween(l, r);
        sum += t.Min;
    }
}
Console.WriteLine(sum);

我使用dotPeek反编译了System.dll,这是我得到的:

public TreeSubSet(SortedSet<T> Underlying, T Min, T Max, bool lowerBoundActive, bool upperBoundActive)
    : base(Underlying.Comparer)
{
    this.underlying = Underlying;
    this.min = Min;
    this.max = Max;
    this.lBoundActive = lowerBoundActive;
    this.uBoundActive = upperBoundActive;
    this.root = this.underlying.FindRange(this.min, this.max, this.lBoundActive, this.uBoundActive);
    this.count = 0;
    this.version = -1;
    this.VersionCheckImpl();
}

internal SortedSet<T>.Node FindRange(T from, T to, bool lowerBoundActive, bool upperBoundActive)
{
  SortedSet<T>.Node node = this.root;
  while (node != null)
  {
    if (lowerBoundActive && this.comparer.Compare(from, node.Item) > 0)
    {
      node = node.Right;
    }
    else
    {
      if (!upperBoundActive || this.comparer.Compare(to, node.Item) >= 0)
        return node;
      node = node.Left;
    }
  }
  return (SortedSet<T>.Node) null;
}

private void VersionCheckImpl()
{
    if (this.version == this.underlying.version)
      return;
    this.root = this.underlying.FindRange(this.min, this.max, this.lBoundActive, this.uBoundActive);
    this.version = this.underlying.version;
    this.count = 0;
    base.InOrderTreeWalk((TreeWalkPredicate<T>) (n =>
    {
      SortedSet<T>.TreeSubSet temp_31 = this;
      int temp_34 = temp_31.count + 1;
      temp_31.count = temp_34;
      return true;
    }));
}

所以,FindRange显然是O(log N),但在此之后我们调用VersionCheckImpl ...它会对找到的子树执行线性时间遍历,仅用于重新计算其节点!

  1. 为什么你需要一直进行这种遍历?
  2. 为什么.NET不包含O(log N)方法来基于密钥分割树,比如C ++或Java?在许多情况下,真的非常有帮助。

1 个答案:

答案 0 :(得分:19)

关于version字段

UPDATE1:

在我的记忆中,BCL中的很多(可能是所有?)集合都有字段version

首先,关于foreach

根据此msdn link

  

foreach语句为数组或对象集合中的每个元素重复一组嵌入式语句。 foreach语句用于迭代集合以获取所需信息,但不应用于更改集合的内容以避免不可预测的副作用。

在许多其他馆藏中,version受到保护,foreach

期间未修改数据

例如,HashTable的{​​{1}}:

MoveNext()

但是在public virtual bool MoveNext() { if (this.version != this.hashtable.version) { throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion")); } .......... } 的{​​{1}}方法中:

SortedSet<T>

UPDATE2:

但是O(N)循环不仅可以用于MoveNext(),还可以用于public bool MoveNext() { this.tree.VersionCheck(); if (this.version != this.tree.version) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); } .... } 属性。

因为MSDN of GetViewBetween说:

  

此方法返回lowerValue和upperValue之间的元素范围视图,由comparer ....定义。您可以在视图和底层SortedSet(Of T)中进行更改< /强>

因此,对于每次更新,都应该同步version字段(键和值已经相同)。确保Count正确

有两项政策可以实现目标:

  1. 微软
  2. Mono的
  3. First.MS,在他们的代码中,他们牺牲count的表现并赢得Count财产的表现。

    GetViewBetween()是同步Count属性的一种方式。

    二,单声道。在单声道代码中,VersionCheckImpl()更快,但在Count方法中:

    GetViewBetween()

    始终是O(N)操作!