如何在遍历时添加要设置的元素?

How to add elements to set while traversing it?

本文关键字:设置 元素 添加 遍历      更新时间:2023-10-16

我们有一个固定的S {1,10,100,1000,10000}。现在我们输入一个整数x(比如x=4)。

现在我们必须将 set 乘积的每个元素都添加到集合本身x。所以终于

S={1,10,100,1000,10000,4,40,400,4000,40000}

[S最初不限于 5 个条目]

我们只需要访问集合中的初始元素。 我尝试了以下方法:

for(auto i=s.begin();i!=s.end();i++)
{
s.insert((*i)*x);
}

这不会给出预期的结果,因为集合的大小不断增加。

我尝试的另一种方法是将所有(*i)*x倍数存储在另一个临时集合/向量中,并在以后将其与 s 合并。 但由于原始数据集很大,它加剧了时间复杂度。 任何优化?

由于std::set是有序的,并且迭代器不会因插入而失效,因此只要不插入到仍有待迭代的范围,就可以在迭代时简单地插入。

如果我们可以假设所有数字都是正数,那么我们可以反向迭代,因为乘法的结果总是大于输入:

for (auto it = S.rbegin(); it != S.rend(); ++it)
S.insert(*it*x);

如果x为负数并且 set 仅包含正数,则迭代顺序无关紧要。如果集合可能包含负数,则这变得更具挑战性。


但由于原始数据集很大,它加剧了时间复杂度。

将 N 个元素插入std::set是 O(N log N)。合并std::sets 是 O(N log N)。合并方法不会降低渐近时间复杂度。

但是,如果您要使用std::unordered_set,则在平均情况下合并方法将为O(N)。然而,在最坏的情况下,它仍然是 O(N log N)。我建议将合并方法与无序集一起使用。

你来了。

#include <iostream>
#include <set>
#include <iterator>
std::set<int> & update( std::set<int> &s, int value )
{
for ( auto it = rbegin( s ); it != rend( s ); ++it )
{
s.insert( *it * value );
}
return s;
}
int main() 
{
std::set<int> s = { 1, 10, 100, 1000, 10000 };
for ( const auto &value : s )
{
std::cout << value << ' ';
}
std::cout << 'n';
for ( const auto &value : update( s, 4 ) )
{
std::cout << value << ' ';
}
std::cout << 'n';
return 0;
}

程序输出为

1 10 100 1000 10000 
1 4 10 40 100 400 1000 4000 10000 40000 

根据 C++ 20 标准(22.2.6 关联容器)

9 插入和放置构件不影响 容器的迭代器和引用,擦除成员应 仅使迭代器和对已拭除元素的引用失效。

或更通用的方法

#include <iostream>
#include <set>
#include <iterator>
std::set<int> & update( std::set<int> &s, int value )
{
auto partition = s.lower_bound( 0 );
for ( auto it = rbegin( s ); it != std::set<int>::reverse_iterator( partition ); ++it )
{
s.insert( *it * value );
}
for ( auto it  = begin( s ); it != partition; ++it )
{
s.insert( *it * value );
}

return s;
}
int main() 
{
std::set<int> s = { -10000, -1000, -100, -10, -1, 1, 10, 100, 1000, 10000 };
for ( const auto &value : s )
{
std::cout << value << ' ';
}
std::cout << 'n';
for ( const auto &value : update( s, 4 ) )
{
std::cout << value << ' ';
}
std::cout << 'n';
return 0;
}

程序输出为

-10000 -1000 -100 -10 -1 1 10 100 1000 10000 
-40000 -10000 -4000 -1000 -400 -100 -40 -10 -4 -1 1 4 10 40 100 400 1000 4000 10000 40000 

如评论中所述,使用临时集是最简单的。对于 C++17,您可以将std::set::merge与转换为右值的临时集一起使用:

#include <algorithm>
std::set<int> s2;
std::transform(s1.cbegin(), s1.cend(), std::inserter(s2, s2.end()),
[](int orig){ return 4*orig; });
s1.merge(std::move(s2));

否则,请注意,插入集的迭代器不会失效。将此与集合是有序的事实结合起来,在您描述的场景(缩放现有值大于原始值,但小于或等于下一个现有值)的情况下,您可以在这样的循环中执行此操作:

for (auto it = s1.begin(); it != s1.end(); ++it)
it = s.insert(*it*4).first;

为了减少限制,您可以使用以下更详细的循环:

for (std::set<int>::iterator it1 = s.begin(), it2; it1 != s.end();
it1 = std::next(it1) == it2 ? std::next(it1, 2) : it2)
it2 = s.insert(*it1*4).first;