考虑一个二维向量vector < vector <int> > N
,让我们说它的内容如下:
1 1 1 1
2 2 2 2
3 3 3 3
4 4 4 4
所以这里N的大小是4,即N.size() = 4
现在,请考虑以下代码:
int i = 0;
while(N != empty()){
N.erase(i);
++i;
}
我只为这段代码计算了N的各种大小的时间,结果如下:
N的大小为1000 执行时间:0.230000s
N的大小是10000 执行时间:22.900000s
N的大小是20000 执行时间:91.760000s
N的大小是30000 执行时间:206.620000s
N的大小是47895 执行时间:526.540000s
我的问题是为什么这个功能如此昂贵?如果是这样,那么许多程序中的条件擦除语句可能会因为这个功能而永远存在。当我在std::map
中使用擦除功能时也是如此。有没有替代这个功能。像Boost这样的其他图书馆会提供吗?
请不要说我可以N.erase()
作为一个整体,因为我只是想分析这个功能。
答案 0 :(得分:15)
考虑删除向量的第一个元素时会发生什么。向量的其余部分必须被一个索引“移动”,这涉及复制它。尝试从另一端擦除,看看是否有所作为(我怀疑它会...)
答案 1 :(得分:6)
因为你的算法是O(n ^ 2)。每次调用erase
都会强制vector
移回已擦除元素后的所有元素。所以在你的4元素向量循环中,第一个循环导致3个元素被移位,第二个迭代导致1个元素被移位,之后你有未定义的行为。
如果你有8个元素,第一个迭代将移动7个元素,下一个将移动5个元素,下一个将移动3个元素,最后的枚举将移动1个元素。 (再次,你有未定义的行为)
当遇到这种情况时,通常应该使用标准算法(即std::remove
,std::remove_if
),因为它们在容器中运行一次并转换典型的O(n ^ 2)算法进入O(n)算法。有关更多信息,请参阅Scott Meyers的“有效STL”第43项:首选算法调用显式循环。
答案 2 :(得分:2)
std :: vector在内部只是一个元素数组。如果删除中间的元素,则必须将其后面的所有元素向下移动。这可能非常昂贵 - 如果元素具有可以完成大量工作的自定义operator=
,则更是如此!
如果你需要erase()
快,你应该使用std::list
- 这将使用双向链表结构,允许从中间快速擦除(但是,其他操作变得稍慢)。如果您只需要快速从列表的 start 中删除,请使用std::deque
- 这将创建一个数组的链接列表,并提供std::vector
的大部分速度优势仍然允许从开始或结束快速擦除。
此外,请注意你的循环使问题变得更糟 - 你首先扫描所有等于零的元素并擦除它们。扫描需要O(n)时间,擦除也需要O(n)时间。然后重复1,依此类推 - 总体而言,O(n ^ 2)时间。如果需要擦除多个值,则应使用迭代器并使用erase()
的迭代器变体自行浏览std::list
。或者,如果您使用vector
,您会发现将其复制到新的矢量中会更快。
至于std::map
(和std::set
) - 这根本不是问题。 std::map
能够随机删除元素,以及随机搜索元素,O(lg n)
时间 - 这对于大多数用途来说非常合理。即使你的天真循环也不应该太糟糕;在一次通过中手动迭代并删除要删除的所有内容会更有效率,但与std::list
和朋友的差距不大。
答案 3 :(得分:1)
vector.erase会在i前进后将所有元素前进1.这是一个O(n)操作。
此外,您按值而不是通过引用传递矢量。
您的代码也不会删除整个矢量。
例如: i = 0 擦除N [0] N = {{2,2,2,2},{3,3,3,3},{4,4,4,4}}
i = 1 擦除N [1] N = {{2,2,2,2},{4,4,4,4}}
i = 2 擦除N [2]没有任何反应,因为最大索引是N [1]
最后,我认为这是vector.erase()的正确语法。您需要将迭代器传递到开始位置以擦除所需的元素。 试试这个:
vector<vector<int>> vectors; // still passing by value so it'll be slow, but at least erases everything
for(int i = 0; i < 1000; ++i)
{
vector<int> temp;
for(int j = 0; j < 1000; ++j)
{
temp.push_back(i);
}
vectors.push_back(temp);
}
// erase starting from the beginning
while(!vectors.empty())
{
vectors.erase(vectors.begin());
}
您还可以将其与结尾的擦除进行比较(它应该明显更快,尤其是在使用值而不是引用时):
// just replace the while-loop at the end
while(!vectors.empty())
{
vectors.erase(vectors.end()-1);
}
答案 4 :(得分:0)
向量是一个在向其添加元素时自动增长的数组。因此,向量中的元素在存储器中是连续的。这允许对元素进行恒定的时间访问。因为它们从最后开始增长,所以它们还需要摊销不变的时间来添加或删除结尾。
现在,当您在中间移除时会发生什么?嗯,这意味着在擦除的元素必须向后移动一个位置之后存在的任何东西。这非常昂贵。
如果你想在中间插入/删除大量的内容,请使用链接列表,例如std :: list of std :: deque。
答案 5 :(得分:0)
正如Oli所说,从向量的第一个元素中删除意味着必须向下复制它后面的元素,以便数组按照需要运行。
这就是为什么链接列表用于从列表中的随机位置删除元素的情况 - 它更快(在较大的列表上),因为没有复制,只重置一些节点指针。