标准库分区算法

时间:2013-11-04 22:12:56

标签: c++ algorithm stl

我写了这个分区函数:

template <class I, class P> I partition(I beg, I end, P p)
{
    I first = beg;
    while(beg != end) {
        if(!p(*beg))
            beg++;
        else {
            // if(beg != first) - EDIT: add conditional to prevent swapping identical elements
            std::swap(*beg, *first);
            first++;
            beg++;
        }
    }
    return first;
}

我用几个输出测试了它,我没有发现它有任何问题。

标准库分区功能相当于:

template <class BidirectionalIterator, class UnaryPredicate>
  BidirectionalIterator partition (BidirectionalIterator first,
                                   BidirectionalIterator last, UnaryPredicate pred)
{
  while (first!=last) {
    while (pred(*first)) {
      ++first;
      if (first==last) return first;
    }
    do {
      --last;
      if (first==last) return first;
    } while (!pred(*last));
    swap (*first,*last);
    ++first;
  }
  return first;
}

后者似乎要复杂得多并且有嵌套循环。我的版本有问题吗?如果不是为什么更复杂的版本?

以下是使用以下谓词的输出:

bool greaterthantwo(double val)
{
    return val > 2;
}

MAIN

std::vector<double> test{1,2,3,4,2,5,6,7,4,8,2,4,10};
std::vector<double>::iterator part = ::partition(test.begin(), test.end(), greaterthantwo);
for(const auto &ref:test)
    std::cout << ref << " ";
std::cout << std::endl;
for(auto it = part; it != test.end(); it++)
    std::cout << *it << " ";
std::cout << std::endl;

OUTPUT

3 4 5 6 7 4 8 4 10 2 2 2 1 
2 2 2 1 

3 个答案:

答案 0 :(得分:6)

您的版本接近Nico Lomuto partition。这样的partition适用于ForwardIterator s并且是半stable(第一部分是稳定的,在某些情况下可能很有用)。

您引用的标准库实现版本接近C. A. R. Hoare在他的论文“Quicksort”中描述的partition。它适用于BidirectionalIterator s,并不意味着任何稳定性。

让我们根据以下情况对它们进行比较:

FTTTT

转发partition将如下进行:

FTTTT
TFTTT
TTFTT
TTTFT
TTTTF

在每次迭代时产生swap,除了第一次,而双向分区将通过以下排列:

FTTTT
TTTTF

对于所有迭代只产生一个swap

此外,一般情况下,双向最多会执行N / 2 swap s,而正向版最多可以执行~N swap s。

C ++ 98/03中的

std::partition适用于BidirectionalIterator s,但在C ++ 11中,它们放宽了ForwardIterator的要求(但是,它不一定是半稳定)。复杂性要求:

  

复杂性:如果ForwardIterator符合BidirectionalIterator的要求,则最多(last - first)/ 2次互换完成;否则最多last - first掉期就完成了。完全是最后 - 谓词的第一个应用程序已经完成。

正如您所看到的,标准库的实现很可能会使用Lomuto的partition ForwardIterator和Hoare partitionBidirectionalIterator

Alexander Stepanov在与Notes on Programming共同撰写的Elements of ProgrammingPaul McJones中讨论了partition个问题


Live Demo

#include <initializer_list>
#include <forward_list>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <list>
using namespace std;

int counter = 0;

struct T
{
    int value;
    T(int x = 0) : value(x) {}
    T(const T &x)
    {
        ++counter;
        value = x.value;
    }
    T &operator=(const T &x)
    {
        ++counter;
        value = x.value;
        return *this;
    }
};
auto pred = [](const T &x){return x.value;};

template<typename Container>
void test()
{
    Container l = {0, 1, 1, 1, 1};
    counter = 0;
    partition(begin(l), end(l), pred);
    cout << "Moves count: " << counter << endl;
}

int main()
{
    test<forward_list<T>>();
    test<list<T>>();
}

输出是:

Moves count: 12
Moves count: 3

swap为3 move s)

答案 1 :(得分:2)

您的功能存在严重缺陷。如果序列的初始元素满足谓词,它会自动交换满足谓词的每个元素。

答案 2 :(得分:1)

来自STL partition description

复杂性 第一个和最后一个之间的距离线性:对每个元素应用pred,并且可能交换其中的一些元素(如果迭代器类型是双向的,最多是交换的一半,否则最多那么多)。

在您的实施中,您可以进行更多交换。