stable_partition 并获得 O(nlogn) 交换

Posted

技术标签:

【中文标题】stable_partition 并获得 O(nlogn) 交换【英文标题】:stable_partition and getting O(nlogn) swaps 【发布时间】:2018-09-16 15:34:17 【问题描述】:

在en.cppreference.com 中,我们看到std::stable_partition 执行O(n) 交换,如果我们被允许使用额外的内存。这个,我看得出来。每次我们在谓词为假的范围内找到一个元素时,我们都会将其交换到另一个缓冲区中。最后,我们可以将这个额外的缓冲区复制到我们成功部分的末尾。 [我还假设,在这种情况下,stable_partition 可以仅使用 Forward 迭代器来实现]

我不明白的是,链接说stable_partition 最多执行 O(nlogn) 交换,如果我们不允许使用额外的内存。这是我的尝试。

#include <utility>

namespace cho 

    template <typename BidirIt, typename Pred>
    BidirIt stable_partition(BidirIt begin, BidirIt end, Pred p) 
        BidirIt next_p = begin;
        for(BidirIt it = begin; it != end; ++it) 
            if(it == begin && p(*it)) 
                next_p++;
                continue;
            
            if(p(*it)) 
                std::swap(*next_p++, *it);

                // riplle back swapped element to keep stability
                BidirIt cur = it;
                do 
                    BidirIt prev = --cur; cur++;
                    std::swap(*cur--, *prev--);
                 while(cur != next_p);
            
        
        return next_p;
    

    template <typename ForwardIt, typename Pred>
    ForwardIt partition(ForwardIt begin, ForwardIt end, Pred p) 
        ForwardIt next_p = begin;
        for(ForwardIt it = begin; it != end; ++it) 
            if(p(*it)) 
                std::swap(*next_p++, *it);
            
        
        return next_p;
    
 

在这种情况下,我会在交换后回复。因此,如果两个连续真实案例之间的距离为k,我将执行k 交换。对于我的算法,我认为最坏的情况发生在范围被反向分区时。如果有p 项的谓词为假,n-p 项的谓词为真,我将得到 O((n - p) * p) 次交换。我想到了这一点,但我看不出我怎样才能得到最坏的情况 O(nlogn)。

LLVM 中的实现,我检查过但无法真正了解 O(nlogn) 交换是如何实现的。

PS:我的实现可能是错误的。我用几个输入测试了它,但就是这样。

【问题讨论】:

Comparison of sorting algorithms。 O(n log n)中有很多稳定且有效的算法 【参考方案1】:

递归思考。

如果左半边和右半边都是稳定分区的,如

0...01...10...01...1
     b    m    e

剩下的唯一操作是旋转b, e 范围,将m 带到b 所在的位置。这将需要O(n) 交换。现在想想递归,稳定分区两半。将有O(log n) 级别的递归,总计O(n log n) 交换。概括地说,

iter stable_partition(begin, end) 
    if (end - begin < 2)
        return;
    iter mid = begin + (end - begin) / 2;
    iter left_break = stable_partition(begin, mid);
    iter right_break = stable_partition(mid, end);
    return rotate(left_break, mid, right_break);

当然,您必须仔细考虑rotate 应该返回什么。

【讨论】:

【参考方案2】:

我不懂 C++,所以我无法为你编写它,但如果你有一个稳定的排序实现可用,这似乎很简单。好吧,这个实现还需要就地排序,因为您需要不使用任何额外的内存。假设有这样的排序实现,只需按照以下顺序关系对元素进行排序:

R(x, y) =  0 if  p(x) ==  p(y)
R(x, y) = -1 if  p(x) && !p(y)
R(x, y) =  1 if !p(x) && p(y) 

出于兴趣,哪种排序算法适合这种情况?事实证明,似乎没有多少人能打勾所有的方框,请参阅here。

【讨论】:

谢谢,很明显它必须与稳定排序有关,但无法提出这样的关系。另外,我开始循环,认为稳定排序需要一个稳定的分区。 似乎这样的排序并不容易找到,但其中列出的一些算法符合所有条件:en.wikipedia.org/wiki/… 你不需要“排序”来做一个稳定的分区。您只需要一个临时缓冲区。您对元素进行 1-pass,如果谓词为假,则将元素移动到缓冲区,如果谓词为真,则覆盖元素。完成后,将临时缓冲区写回返回 true 的最后一个元素之后。工作完成 不使用缓冲区是问题的要求......它必须是没有额外内存的就地分区 DIci:任何自适应算法都不可能增加内存:stable_sort、stable_partition、inplace_merge。我似乎有复杂的算法试图管理这个。至少,您需要堆栈上的 1 个元素。其他一切都是通过轮换完成的。

以上是关于stable_partition 并获得 O(nlogn) 交换的主要内容,如果未能解决你的问题,请参考以下文章

为forward_list实现stable_partition

STL 之 partition()方法 和 stable_partition()方法

蚂蚁集团获得Spider冠军,登上两项NL2SQL权威榜单榜首

是否可以在Swi-Prolog中获得以毫秒为单位的经过时间?

如何获得iframe的高度?

34-nl 简明笔记