繁体   English   中英

前向迭代器上的 stable_partition

[英]stable_partition on forward iterators

1.在当前的标准草案中, std::stable_partition的规范

 template<class BidirectionalIterator, class Predicate> BidirectionalIterator stable_partition( BidirectionalIterator first, BidirectionalIterator last, Predicate pred);

我没有发现BidirectionalIterator应该是一个双向迭代器的要求,但这个名字表明是这样。 (见下文)

2.在SGI STL中,规范

 template <class ForwardIterator, class Predicate> ForwardIterator stable_partition( ForwardIterator first, ForwardIterator last, Predicate pred);

对类型的要求: ForwardIterator是 Forward Iterator 的模型。

当前标准和 SGI 版本的复杂性规范是相同的:最多N log N交换,如果有足够的额外内存和谓词和投影的准确N应用,则只有O(N)交换。

3. libstdc++libc++ 中的声明如下所示:

template<typename ForwardIterator, typename Predicate>
ForwardIterator stable_partition(
    ForwardIterator first, ForwardIterator last, Predicate pred);

在 GCC 和 Clang 中, std::stable_partition确实适用于前向迭代器。 例如:

int main() {
    std::forward_list<int> list{1, 4, 5, 2, 3, 0};
    std::stable_partition(list.begin(), list.end(), [](int i) { return i < 3;});

    for (auto v : list)
        std::cout << v << ' ';
}

编译并产生正确的输出 Microsoft 的编译器无法编译此代码(没有--运算符)。 英特尔的一个成功了。

我有两个相关的问题:

  • std::stable_partition是否至少接受标准的双向迭代器,或者名称BidirectionalIterator是否具有误导性?
  • 如果它确实只接受双向迭代器,为什么放弃了对前向迭代器的支持?

编辑

发现这个条款

如果算法的模板参数名为BidirectionalIteratorBidirectionalIterator1BidirectionalIterator2 ,则模板参数应满足Cpp17BidirectionalIterator要求。

所以,只剩下第二个问题了。

首先,没有放弃支持, std::stable_partition一直需要BidirectionalIterator标准。 这并不意味着库的实现者不允许对输入参数提供较少的限制(如果它继续遵守标准 ofc 的其他部分)。 因此 Gcc、Clang 和 Intel 使用他们的权利并使代码更加通用。 您可以将其视为标准库的编译器扩展。

话虽如此,可能有人会问为什么标准在这里需要BidirectionalIterator 我想这是可能的,因为标准的作者没有看到没有这个要求就符合复杂性要求的方法。 gcc 的作者可能找到了一种比标准预期的更好的方法。 查看 gcc 源代码有点证实了这一点。 https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/stl_algo.h#L1613

编辑:

我已经深入研究了 GCC 库的实现,我想我明白了。 ForwardIteratorBidirectionalIteratorRandomAccessIterator实现对于std::stable_partition是不同的。 这是由于分区算法使用的std::rotate不同实现。 因此,对于前向迭代器,交换次数更大并且可以超过(last - first) * log(last - first) 这里这里

这个问题似乎有历史原因而不是数学原因。 翻阅 Alexander Stepanov 的论文,我发现了这篇:“分区和相关函数”。

它包含以下段落:

备注:有趣的是,这种优秀的算法不在需要双向迭代器进行分区的 C++ 标准中。 我知道、实施和教授这个算法已经有一段时间了——因为我第一次在 80 年代中期在 CACM 的 Bentley 专栏中读到了它。 但是我最初的 STL 提议以某种方式为partitionstable_partition指定了双向迭代器 它们都在 SGI STL 中得到了纠正,但大多数供应商仍然落后。 这件小事已经困扰我 10 多年了; 最麻烦的部分是遗漏的事实。 它怎么发生的? 我怀疑这个解释很简单:虽然在 90 年代初我已经理解了将每种算法减少到最低要求的想法,而且我也知道当我们对数据了解得更多时,可以使用更好的算法来实现相同的操作。如果有这样的算法,我还没有完全意识到需要为最弱的情况提供算法。 又花了几年时间才明白“填充算法空间”的重要性。

下面的简单算法

template <typename I, // I models Forward Iterator
          typename N, // N models Integer
          typename P> // P models Unary Predicate
pair<I, I> stable_partition_inplace_n(I f, N n, P p)
{
    if (n == 0) return make_pair(f, f);
    if (n == 1) {
        I l = successor(f);
        if (p(*f)) l = f;
        return make_pair(f, l);
    }
    pair<I, I> i = stable_partition_inplace_n(f, n/2, p);
    pair<I, I> j = stable_partition_inplace_n(i.second, n – n/2, p);
    return make_pair(rotate(i.first, i.second, j.first), j.second);
}

在那篇论文中给出。 它与前向迭代器一起工作并在最坏的情况下执行O(N log N)交换。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM