我已经阅读了几篇文章,声明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)时间内执行?
答案 0 :(得分:24)
通常List<T>::RemoveAt
是O(N),因为需要在索引向上移动数组中的一个插槽之后。但是对于从列表末尾删除的特定情况,不需要移位,因此O(1)
答案 1 :(得分:5)
删除最后一项实际上将是O(1)
操作,因为仅在这种情况下List
不会移动数组中的下一项。这是来自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);
答案 2 :(得分:2)
这应该给你一个想法
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++;
}
答案 3 :(得分:0)
渐近地说,O(N)是方法本身的最坏情况时间复杂度,其中N是计数。它的表现不会比这差。
实际上,它将是O(N-I)的顺序(忽略恒定的时间开销),其中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);
答案 4 :(得分:-2)
在我看来,如果这实际上与您的申请相关,那么您可以在比提出问题所花费的时间更短的时间内对其进行测量。现在你至少有两个相互矛盾的答案,所以无论如何你都要测试它。
我想说的是,除非MSDN文档说列表末尾的项目的removeAt是O(1),否则你真的不能指望它以这种方式工作,它可能会改变在任何给定的.NET更新中。就此而言,对于所有人而言,不同类型的行为可能会有所不同。
如果List是要使用的“自然”数据结构,则使用它。如果从列表中删除项目最终成为您的分析的热点,那么也许是时候实现自己的类了。