.NET 4.0以降では、クラス_SortedSet<T>
_にGetViewBetween(l, r)
というメソッドがあり、指定された2つの間のすべての値を含むツリーパーツのインターフェイスビューを返します。 _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
...を呼び出します。これは、ノードを再カウントするためだけに、見つかったサブツリーの線形時間トラバーサルを実行します。
O(log N)
メソッドが含まれていないのはなぜですか?それは本当に多くの状況で役立ちます。version
フィールドについて私の記憶では、BCLの多くの(おそらくすべて?)コレクションにはフィールドversion
があります。
foreach
について:これによると msdn link
Foreachステートメントは、配列またはオブジェクトコレクションの要素ごとに埋め込みステートメントのグループを繰り返します。 foreachステートメントは、コレクションを反復処理して必要な情報を取得するために使用されますが、予期しない副作用を回避するためにコレクションのコンテンツを変更するために使用しないでください。
他の多くのコレクションでは、version
は保護されており、foreach
中にデータは変更されません。
たとえば、HashTable
のMoveNext()
:
_public virtual bool MoveNext()
{
if (this.version != this.hashtable.version)
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));
}
..........
}
_
しかし、_SortedSet<T>
_のMoveNext()
メソッドでは:
_public bool MoveNext()
{
this.tree.VersionCheck();
if (this.version != this.tree.version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
....
}
_
しかし、O(N)ループは、version
だけでなく、Count
プロパティに対しても発生する可能性があります。
GetViewBetweenのMSDN が言ったので:
このメソッドは、比較子によって定義された、lowerValueとupperValueの間にある要素の範囲のビューを返します....ビューと基になるSortedSet(Of)の両方で変更を加えることができます。 T)。
したがって、更新のたびに、count
フィールドを同期する必要があります(キーと値はすでに同じです)。 Count
が正しいことを確認するには
目標を達成するための2つのポリシーがありました。
First.MSは、コード内でGetViewBetween()
のパフォーマンスを犠牲にし、Count
プロパティのパフォーマンスを獲得します。
VersionCheckImpl()
は、Count
プロパティを同期する1つの方法です。
第二に、モノ。 monoのコードでは、GetViewBetween()
の方が高速ですが、GetCount()
methodでは次のようになります。
_internal override int GetCount ()
{
int count = 0;
using (var e = set.tree.GetSuffixEnumerator (lower)) {
while (e.MoveNext () && set.helper.Compare (upper, e.Current) >= 0)
++count;
}
return count;
}
_
それは常にO(N)操作です!