标准库分区算法
Standard library partition algorithm
我写了这个分区函数:
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
您的版本接近 Nico Lomuto partition
.这种partition
在ForwardIterator
上工作并且是半稳定的(第一部分是稳定的,在某些情况下可能很有用)。
您引用的标准库实现版本接近 C. A. R. Hoare 在他的论文"Quicksort"中描述partition
。它适用于BidirectionalIterator
,并不意味着任何稳定性。
让我们在以下情况下比较它们:
FTTTT
转发partition
将像这样进行:
FTTTT
TFTTT
TTFTT
TTTFT
TTTTF
导致每次迭代都swap
,但第一次除外,而双向分区将经历以下排列:
FTTTT
TTTTF
导致所有迭代仅产生一个swap
。
此外,在一般情况下,双向最多可以做 N/2 swap
秒,而正向版本最多可以做 ~N swap
秒。
std::partition
在 C++98/03 中适用于 BidirectionalIterator
s,但在 C++11 中,他们将要求放宽到 ForwardIterator
s(尽管它不一定是半稳定的)。复杂性要求:
复杂性:如果
ForwardIterator
满足BidirectionalIterator
的要求,则最多(last
-first
)/2次掉期;否则最多last
-first
掉期。正好最后 - 谓词的第一次应用完成。
如您所见,标准库的实现很可能会将 Lomuto 的 partition
用于 ForwardIterator
s,将 Hoare 的 partition
用于 BidirectionalIterator
s。
亚历山大·斯捷潘诺夫(Alexander Stepanov partition
与保罗·麦克琼斯(Paul McJones)合著的《编程笔记》(Notes on Programming and Elements of Programming)中讨论了这个问题
现场演示
#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
秒)
您的函数存在严重缺陷。如果序列的初始元素满足谓词,它将满足谓词的每个元素与自身交换。
来自 STL 分区说明
复杂性第一个和最后一个之间的距离是线性的:将 pred 应用于每个元素,并可能交换其中一些元素(如果迭代器类型是双向的,则最多交换一半,否则最多交换那么多)。
在您的实现中,您可以交换更多。
- 为什么这个运算符<重载函数对 STL 算法不可见?
- 基于ELO的团队匹配算法
- C++选择排序算法中的逻辑错误
- 有没有办法将谓词中的元素偏移量传递给 std 算法?
- C++A*算法并不总是在路径中具有目标节点
- 排序算法c++
- 是否有类似std::lower_bound的函数,而不需要排序/分区输入
- 构建可组合有向图(扫描仪生成器的汤普森构造算法)
- 算法问题:查找从堆栈中弹出的所有序列
- 下面是排序算法O(n)吗
- Coursera DSA 算法工具箱第 4 周第 2 个问题 - 分区纪念品
- 对多个(可能)重叠范围进行分区的最简单算法
- 迭代快速排序方法的分区算法问题
- C++分区算法
- 是否存在用于按以下方式对两个范围进行排序和分区的标准算法?
- 为什么我的递归快速排序算法有如此不平衡的分区
- 使用霍尔分区方案的快速排序算法返回原始未排序列表
- 标准库分区算法
- 矢量元素在分区算法中不交换
- 回文分区的这种算法的时间复杂度是多少?