List.RemoveAt()がO(n)時間内にあることを示すいくつかの記事を読みました。
私が次のようなことをした場合:
var myList = new List<int>();
/* Add many ints to the list here. */
// Remove item at end of list:
myList.RemoveAt(myList.Count - 1); // Does this line run in O(n) time?
リストの数を減らす必要があるだけなので、リストの最後から削除するにはO(1)にする必要があります。
この動作を実現するには、独自のクラスを作成する必要がありますか、それともC#リストの最後にあるアイテムの削除はO(1)時間ですでに実行されていますか?
一般に List<T>::RemoveAt
is O(N)インデックスの後に要素を配列のスロットにシフトする必要があるためです。ただし、リストの最後から削除する特定のケースでは、シフトは必要ありません。その結果、O(1)になります。
この場合のみList
は配列内の次の項目をシフトしないため、最後の項目の削除は実際にはO(1)
操作になります。これがReflectorのコードです:
this._size--;
if (index < this._size) // this statement is false if index equals last index in List
{
Array.Copy(this._items, index + 1, this._items, index, this._size - index);
}
this._items[this._size] = default(T);
これはあなたにアイデアを与えるはずです
public void RemoveAt(int index) {
if ((uint)index >= (uint)_size) {
ThrowHelper.ThrowArgumentOutOfRangeException();
}
_size--;
if (index < _size) {
Array.Copy(_items, index + 1, _items, index, _size - index);
}
_items[_size] = default(T);
_version++;
}
漸近的に話す場合、O(N)は、メソッド自体の最悪の場合の時間計算量です。ここで、Nはカウントです。それよりも悪いパフォーマンスを示すことはできません。
実際には、O(N-I)(一定の時間オーバーヘッドを無視)の順序になります。ここで、Iはインデックスです。これは、指定されたインデックスを超えるすべてのアイテムが必要であるため、控除できます。リスト内でそれぞれ前の位置に移動しました。
これを直感的に確認するには、Nが100で、インデックスが99(最後の要素)の場合、「シフト」する必要のある要素はありません。最後の要素を削除するだけです(または、データ構造のサイズを変更せずにカウントを減らすだけです)。 )。
同様に、Nが100で、インデックスが0(最初の要素)の場合、99シフトを行う必要があります。
次のコードを実行して、自分の目で確かめてください。
int size = 1000000;
var list1 = new List<int>();
var list2 = new List<int>();
for (int i = 0; i < size; i++)
{
list1.Add(i);
list2.Add(i);
}
var sw = Stopwatch.StartNew();
for (int i = 0; i < size; i++)
{
list1.RemoveAt(size-1);
list1.Add(0);
}
sw.Stop();
Console.WriteLine("Time elapsed: {0}", sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i = 0; i < size; i++)
{
list2.RemoveAt(0);
list2.Add(0);
}
sw.Stop();
Console.WriteLine("Time elapsed: {0}", sw.ElapsedMilliseconds);