具有昂贵或已删除的默认构造函数的最小n个元素

min n elements with expensive or deleted default constructor

本文关键字:构造函数 元素 默认 删除      更新时间:2023-10-16

给定一个通常未排序数据(如assert(std::is_same< typeof(v), V >::value);)的数组v(一些STL容器,例如std::vector< double >)。在数组的元素上定义了比较运算符,比如std::less。您需要创建一个具有n最小元素的数组(从v复制),但这些元素是,不是默认可构造的(或者是昂贵的操作)。如何使用STL?需要非修改序列算法。

最初被视为使用std::back_insert_iterator解决问题的一种方法,但如进一步解释的那样,存在一些混淆:

assert(!std::is_default_constructible< typename V::value_type >::value); // assume
template< class V >
V min_n_elements(typename V::const_iterator begin, typename V::const_iterator end, typename V::size_type const n)
{
assert(!(std::distance(begin, end) < n));
V result; // V result(n); not allowed
result.reserve(n);
std::partial_sort_copy(begin, end, std::back_inserter(result), /*What should be here? mb something X(result.capacity())?*/, std::less< typename V::value_type >());
return result;
}

我想找到在时间和内存方面最优的解决方案(O(1)额外内存和<=O(std::partial_sort_copy)时间消耗)。总的来说,算法应该在以下数量的存储器上操作:不可修改源vv.size()元素作为输入,新建元素的n作为输出,所有这些元素都是源阵列vn最小元素的副本。仅此而已。我认为这是一个现实的极限。

EDIT:用堆重新实现:

template< class V > 
V min_n_elements(typename V::const_iterator b, typename V::const_iterator e, typename V::size_type const n) {
assert(std::distance(b, e) >= n);
V res(b, b+n);
make_heap(res.begin(), res.end());
for (auto i=b+n;  i<e;  ++i) {
if (*i < res.front())  {
pop_heap(res.begin(), res.end());
res.back() = *i;
push_heap(res.begin(), res.end());
}
}
return std::move(res);
}

除非您还需要对这些元素进行排序,否则使用std::nth_element,然后使用std::copy可能是最简单、最快的。

template <class InIter, class OutIter>
min_n_elements(InIter b, InIter e, OutIter o, InIter::difference_type n) {
InIter pos = b+n;
std::nth_element(b, pos, e);
std:copy(b, pos, o);
}

std::nth_element不仅找到给定的元素,而且保证那些小于该元素的元素是它的"左边"的两个,而那些大于该元素的则是在它的"右边"。

不过,这确实稍微回避了真正的问题——它并没有真正为结果创建容器,而是简单地期望用户创建一个正确类型的容器,然后提供一个迭代器(例如,back_insert_iterator)来将数据放在正确的位置。同时,我认为这确实是正确的做法——找到N个最小元素的算法和为目的地选择容器是分开的。

如果你真的想把结果放在一个特定的容器类型中,那应该不是很难:

template <class V>
V n_min_element(V::iterator b, V::iterator e) { 
V::const_iterator pos = b+n;
nth_element(b, pos, e);
V ret(b, pos);
return V;
}

就目前情况来看,它们确实修改了输入中的(元素的顺序),但鉴于您已经说过输入没有排序,我假设它们的顺序无关紧要,所以这应该是允许的。如果你不能做到这一点,下一种可能性是可能创建一个指针集合,并使用一个基于指针进行比较的比较函数,然后对此执行nth_element,最后将指针复制到新集合。

相关文章: