以下代码:
List<Interval> intervals = new List<Interval>();
List<int> points = new List<int>();
//Initialization of the two lists
// [...]
foreach (var point in points)
{
intervals.RemoveAll (x => x.Intersects (point));
}
当列表大小为~10000时,比此快至少100倍:
List<Interval> intervals = new List<Interval>();
List<int> points = new List<int>();
//Initialization of the two lists
// [...]
foreach (var point in points)
{
for (int i = 0; i < intervals.Count;)
{
if (intervals[i].Intersects(point))
{
intervals.Remove(intervals[i]);
}
else
{
i++;
}
}
}
怎么可能?使用“RemoveAll”在引擎盖下执行什么操作?根据{{3}},“RemoveAll”执行线性搜索,因此在O(n)中。所以我希望两者都有类似的表现。
当用“RemoveAt”替换“Remove”时,迭代速度要快得多,与“RemoveAll”相当。但两者“删除”和“删除”都有O(n)复杂性,那么为什么它们之间的性能差异如此之大?难道只是因为“删除(项目)”将列表元素与“项目”进行比较,“RemoveAt”不执行任何比较?
答案 0 :(得分:25)
如果您从List<T>
中移除某个项目,则其后的所有项目都将移回一个位置。因此,如果删除n个项目,很多项目将被移动n次
RemoveAll只会移动一次,您可以在List<T>
的来源中看到:source
另一件事是Remove(T item)
将在整个列表中搜索该项目,因此这是另外n次操作。
与你的问题无关的东西,但无论如何我想指出:
如果你使用for循环从List中删除项目,那么从最后开始就容易得多:
for (int i = intervals.Count - 1; i >= 0; i--)
{
if (intervals[i].Intersects(point))
{
intervals.RemoveAt(i);
}
}
这样,你不需要那个丑陋的else子句
答案 1 :(得分:9)
RemoveAll
元素的条件并移动最多O(n)
元素,可以在n
中完成 n
。
您的循环为O(n^2)
,因为每个Remove
都需要检查n
个元素。即使您将其更改为RemoveAt
,它仍然需要升级到n
元素。
这可能是最快的解决方案:
intervals.RemoveAll(x => points.Any(x.Intersects));
答案 2 :(得分:3)
List
是一个数组,从数组中删除一个元素需要将所有元素移到您要删除的元素之后,因此a[i]
会移到a[i-1]
。
重复执行此操作需要多次移动,即使有更多元素符合删除条件。 RemoveAll
可以通过在遍历列表时一次移动元素超过1个索引并找到更多符合删除条件的元素来优化此项。
答案 3 :(得分:0)
区别在于Remove本身是一个O(n),所以得到O(n ^ 2)。
将for
替换为新的集合和作业。
items = items.Where(i => ...).ToList();
此方法与RemoveAll具有相同的算法时间复杂度,但使用额外的O(n)内存。