为什么是List(T)。清除O(N)?

时间:2011-01-25 21:15:34

标签: .net list performance big-o clear

根据the MSDN documentation on the List<T>.Clear method

  

此方法是O(n)操作,   其中n是计数。

为什么O(n)?我问,因为我认为只需在内部分配一个新的List<T>数组即可完成清除T[]。没有其他类可以引用这个数组,所以我没有看到这种方法的危害。

现在,也许这是一个愚蠢的问题......正在分配T[]数组本身O(n)?出于某种原因,我不会这么想;但也许是(我现在缺少一个C.S.学位?)。如果是这样,我想这可以解释它,因为根据上面引用的相同文档,列表的容量保持不变,这意味着需要构建一个相同大小的数组。

(然而,这似乎不是正确的解释,因为文档应该说“其中n是容量” - { {1}} *)。

我只是怀疑这个方法,而不是分配一个新的数组,将当前的所有元素清零;而且我很想知道为什么会这样。

* Hans Passant在对LukeH's answer的评论中指出文档是正确的。 Count仅将Clear中设置的元素清零;它不需要将所有元素“重新归零”。

5 个答案:

答案 0 :(得分:7)

据我所知,当前实现只在其内部T[]数组上调用Array.Clear,而Array.Clear是一个O(n)进程。 (正如Hans在评论中指出的那样,MSDN文档是正确的,在这种情况下n是列表的Count,而不是Capacity。)

但是,即使它只在内部分配一个新的T[]数组,那仍然是一个O(n)进程,因为分配一个大小为n的数组涉及初始化所有n元素为零/空/默认状态。

当然,没有什么可以阻止某种内部欺骗,其中阵列可以用0或42或其他任何长度进行初始化,然后根据需要在运行中再次自动扩展,从而摊销整体O(n)成本

答案 1 :(得分:6)

因为List<T>.Clear()的实现会在列表的backign数组上调用Array.Clear(),并且根据documentation该方法将该数组的所有元素设置为null。< / p>

我猜想清除现有数组而不是创建新数组的原因是.NET团队确定清除现有数组而不是分配新数组更有效。分配新阵列也需要时间/内存,所以这是一种权衡,试图针对常见的使用场景进行优化。

答案 2 :(得分:0)

List<T>.Clear()调用Array.Clear(),如下所示:

public virtual void Clear()
{
    if (this._size > 0)
    {
        Array.Clear(this._items, 0, this._size);
        this._size = 0;
    }
    this._version++;
}

反过来,Array.Clear()调用一个外部函数,它将数组的元素归零为O(n):

[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical, ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static extern void Clear(Array array, int index, int length);

答案 3 :(得分:0)

我从另一个角度提出了这个问题,这里没有回答。我当时以为应该是O(1),因为不需要分配,只需要更改Count标志/指针,因此将每个元素(不再可以访问)重置为默认值看起来很浪费,我想知道为什么要这样做。 / p>

当然,答案是这将适用于ValueTypes / structs,而不适用于类。甚至应该从List的私有数组中取消引用后者,否则它们将不符合垃圾收集的条件。

注意n是Count,而不是Capacity,因为List始终保持一个合约,无论哪些元素不可访问,该合约也都没有值。因此,“计数”和“容量”之间的元素可能从未设置过,或者在删除后已经重新设置。

但是,对于结构体而言,此重置非常浪费,在这种情况下,Clear()可以为O(1)。因此,标准库(现在,Microsoft正在改善C#的性能)中有一个空间用于IList,其中T:struct具有List <>的所有功能以及O(1)Clear()实现。

关于OP分配新的私有数组的建议:首先,您已经具有该选项,如果您要的是此选项,则只需新建List即可。其次,正如LukeH所说,分配是O(n),因为它意味着初始化/重置。第三,比较遍历已分配列表/数组的O(n)与新分配(和取消分配)需要花费的时间是非常棘手的。

答案 4 :(得分:-1)

List<T>列表包含一些对象。分配新的List<T>并没有消除旧列表中的对象,因此它不是clear操作。

操作clear执行线性一次性遍历列表,逐个清除所有元素。由于有n个元素,因此需要O(n)次。

分配T[]取决于是否指定了大小。如果指定了大小,则必须为每个元素留出内存,或至少为该元素指定每个指针。因此需要O(n)。但是,如果我们只是初始化T[]的指针,则需要O(1)时间。

P.S。 CS学位并不自动意味着你知道(或记住)这些东西......伤心,但是,真的。缺乏CS学位并不是有害的。