【发布时间】:2012-04-08 16:59:40
【问题描述】:
在 .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 秒内运行,而 GetViewBetween 的等效 O(log N) 版本将使此代码在 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... 它对找到的子树进行线性时间遍历,仅用于重新计算其节点!
- 为什么需要一直进行这种遍历?
- 为什么 .NET 不包含
O(log N)方法来根据键拆分树,如 C++ 或 Java?它在很多情况下真的很有帮助。
【问题讨论】:
-
嗯,你是对的,破坏它的是 VersionCheckImpl()。检查在 .NET 集合类中是相当神圣的,我想不出更好的方法。只要您使用子集并检查到位,您就会得到O(log n),但是创建它是O(n)。您可以发布到 connect.microsoft.com 以指出这一点,并从内部人员那里获得看法。然而,他们将其关闭为“按设计”的可能性很高。
-
BCL 中的一个令人发指的错误。 GetRange 方法应该比线性过滤器更有效!
-
要求
SortedSet<T>.Countbe O(1)的悲剧后果。 -
我只想指出它并不像线性过滤器那么糟糕。如果我正确理解代码,则只有范围是线性遍历的,而不是整个集合。
-
从 2017 年开始,SortedSet
.GetViewBetween(...) 的 dotnet 核心实现是一个 O(log(n)) 实现。办公室。加上你需要提取的元素。
标签: c# .net complexity-theory sortedset