在.NET 4.0+中,类
然而,事实并非如此。下面的代码运行时间为32秒,而等效的
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
... 它只对找到的子树进行线性遍历来重新计算节点数!
- 为什么你需要一直进行这种遍历?
- 为什么.NET没有像C++或Java那样基于键值拆分树的
O(log N)
方法?在许多情况下,它真的非常有帮助。
SortedSet<T>.Count
的时间复杂度要求为 O(1) 所带来的悲惨后果。 - Raymond Chen