Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/152.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 使用STL迭代器实现Bentley McIlroy三向分区?_C++_Algorithm_Stl_Iterator_Quicksort - Fatal编程技术网

C++ 使用STL迭代器实现Bentley McIlroy三向分区?

C++ 使用STL迭代器实现Bentley McIlroy三向分区?,c++,algorithm,stl,iterator,quicksort,C++,Algorithm,Stl,Iterator,Quicksort,在他们的演讲中,Sedgewick和Bentley提到了一个名为Bentley McIlroy三向分区的快速排序分区步骤的修改版本。这个版本的分区步骤通过总是从剩余的pivot元素中拉出pivot元素的副本,优雅地适应包含相等键的输入,确保在包含重复项的数组上调用时,算法仍然执行良好 此分区步骤的C代码在此处重新打印: void threeWayPartition(Item a[], int l, int r) { int i = l-1, j = r, p = l-1, q = r; I

在他们的演讲中,Sedgewick和Bentley提到了一个名为Bentley McIlroy三向分区的快速排序分区步骤的修改版本。这个版本的分区步骤通过总是从剩余的pivot元素中拉出pivot元素的副本,优雅地适应包含相等键的输入,确保在包含重复项的数组上调用时,算法仍然执行良好

此分区步骤的C代码在此处重新打印:

void threeWayPartition(Item a[], int l, int r)
{ 
  int i = l-1, j = r, p = l-1, q = r; Item v = a[r];
  if (r <= l) return;
  for (;;)
    { 
       while (a[++i] < v) ;
       while (v < a[--j]) if (j == l) break;
       if (i >= j) break;
       exch(a[i], a[j]);
       if (a[i] == v) { p++; exch(a[p], a[i]); }
       if (v == a[j]) { q--; exch(a[j], a[q]); }
    } 
  exch(a[i], a[r]); j = i-1; i = i+1;
  for (k = l; k < p; k++, j--) exch(a[k], a[j]);
  for (k = r-1; k > q; k--, i++) exch(a[i], a[k]);
}
这最终会创建两个在要分区的范围之前的整数,这很好,因为在循环体中,它们在使用之前是递增的。但是,如果我用双向迭代器替换这些索引,那么这段代码将不再定义行为,因为它在排序范围开始之前备份迭代器

我的问题如下-在不重写算法核心的情况下,有没有一种方法可以使这段代码适应使用STL风格的迭代器,因为该算法首先在一个范围的开头备份一个迭代器?现在,我唯一的想法就是引入额外的变量来“假装”我们在第一步备份了迭代器,或者用特殊的迭代器适配器来装饰迭代器,让您只需跟踪在范围开始之前的逻辑步骤数,就可以在开始之前进行备份。这两个看起来都不是很优雅。。。我错过什么了吗?有一个简单的解决方案吗


谢谢

无需实质性重写算法核心

这几乎限制了您尝试绕过边界问题,因此您需要使用自定义迭代器适配器,或者将迭代器包装在
boost::optional
或类似的东西中,以便您知道它是何时第一次访问的

更好的方法是修改算法以适应手头的工具(这正是STL的目的,对不同的迭代器类型使用不同的算法)

我不知道是否,但它以不同的方式描述了算法,不需要迭代器越界


编辑:说到这里,我已经试过了。这段代码未经测试,因为我不知道给定输入时输出应该是什么样子-请参阅注释了解详细信息。它只可能用于双向/随机访问迭代器

#include <algorithm>
#include <iterator>

template <class Iterator>
void three_way_partition(Iterator begin, Iterator end)
{
    if (begin != end)
    {
        typename Iterator::value_type v = *(end - 1);

        // I can initialise it to begin here as its first use in the loop has
        // changed to post-increment (its pre-increment in your original
        // algorithm).
        Iterator i = begin;

        Iterator j = end - 1;

        // This should be begin - 1, but thats not valid, I set it to end
        // to act as a sentinal value, that way I know when im incrementing
        // p for the first time, and can set it to begin.
        Iterator p = end;

        Iterator q = end - 1;

        for (;;)
        {
            while (*(i++) < v);

            while (v < *(--j))
            {
                if (j == begin)
                {
                    break;
                }
            }

            if (std::distance(i, j) <= 0)
            {
                break;
            }

            if (*i == v)
            {
                if (p == end)
                {
                    p = begin;
                }
                else
                {
                    ++p;
                }

                std::iter_swap(p, i);
            }

            if (v == *j)
            {
                --q;
                std::iter_swap(j, q);
            }
        }

        std::iter_swap(i, end - 1);

        j = i - 1;
        i++;

        for (Iterator k = begin; k < p; ++k, --j)
        {
            std::iter_swap(k, j);
        }

        for (Iterator k = end - 2; k > q; --k, ++i)
        {
            std::iter_swap(i, k);
        }
    }
}
#包括
#包括
模板
无效三向分区(迭代器开始、迭代器结束)
{
如果(开始!=结束)
{
typename迭代器::value_type v=*(end-1);
//我可以在这里初始化它,因为它在循环中的第一次使用已经完成
//更改为增量后(其在原始文件中的增量前)
//算法)。
迭代器i=开始;
迭代器j=end-1;
//这应该是begin-1,但这是无效的,我将其设置为end
//作为一个sentinal值,这样我就知道何时递增
//p,并且可以将其设置为开始。
迭代器p=结束;
迭代器q=end-1;
对于(;;)
{
而(*(i++)
不幸的是,表达式“k
if (i >= j) break; 
必须离开,并被替换为

if (i == j) break;

这意味着您需要在“内部”循环中添加额外的条件,以确保j(尤其是)不会减得太多。Net/Net使此算法在双向迭代器上运行时,无法满足“无需实质性重写”的约束。

考虑到该函数所做的所有交换,这不是更容易吗(也许更有效)只需执行以下操作

#include <algorithm>
#include <iterator>

template <class Iterator>
void three_way_partition(Iterator begin, Iterator end)
{
    if (begin != end)
    {
        typename Iterator::value_type v = *(end - 1);

        // I can initialise it to begin here as its first use in the loop has
        // changed to post-increment (its pre-increment in your original
        // algorithm).
        Iterator i = begin;

        Iterator j = end - 1;

        // This should be begin - 1, but thats not valid, I set it to end
        // to act as a sentinal value, that way I know when im incrementing
        // p for the first time, and can set it to begin.
        Iterator p = end;

        Iterator q = end - 1;

        for (;;)
        {
            while (*(i++) < v);

            while (v < *(--j))
            {
                if (j == begin)
                {
                    break;
                }
            }

            if (std::distance(i, j) <= 0)
            {
                break;
            }

            if (*i == v)
            {
                if (p == end)
                {
                    p = begin;
                }
                else
                {
                    ++p;
                }

                std::iter_swap(p, i);
            }

            if (v == *j)
            {
                --q;
                std::iter_swap(j, q);
            }
        }

        std::iter_swap(i, end - 1);

        j = i - 1;
        i++;

        for (Iterator k = begin; k < p; ++k, --j)
        {
            std::iter_swap(k, j);
        }

        for (Iterator k = end - 2; k > q; --k, ++i)
        {
            std::iter_swap(i, k);
        }
    }
}
template <typename For, typename Cmp>
  std::pair<For, For>
  partition_3way(For first, For last,
          typename std::iterator_traits<For>::value_type pivot, Cmp comp)
  {
        For lower = std::partition(first, last, std::bind2nd(comp, pivot));
        For upper = std::partition(lower, last,
                std::not1(std::bind1st(comp, pivot)));
        // return equal range for elements equal to pivot.
        return std::pair<For, For>(lower, upper);
  }
模板
std::pair
分区方式(对于第一个,对于最后一个,
类型名称std::迭代器特征::值类型枢轴,Cmp comp)
{
对于lower=std::partition(第一个,最后一个,std::bind2nd(comp,pivot));
对于上部=标准::分区(下部,最后,
std::not1(std::bind1st(公司、枢轴));
//返回与轴相等的元素的相等范围。
返回标准::对(下、上);
}

当前排名靠前的答案的一个主要问题是,调用
std::distance
在最坏的情况下会使迭代二次化。没有唯一键的序列会导致最坏的情况,这尤其令人遗憾,因为这正是三向分区旨在加速的情况

这将以最佳方式实现Bentley McIlroy 3向分区,以便与双向迭代器一起使用

template <typename Bi1, typename Bi2>
  Bi2 swap_ranges_backward(Bi1 first1, Bi1 last1, Bi2 last2)
  {
        typedef typename std::reverse_iterator<Bi1> ri1;
        typedef typename std::reverse_iterator<Bi2> ri2;
        return std::swap_ranges(ri1(last1), ri1(first1), ri2(last2)).base();
  }

template <typename Bi, typename Cmp>
  std::pair<Bi, Bi>
  partition3(Bi first, Bi last,
    typename std::iterator_traits<Bi>::value_type pivot, Cmp comp)
  {
        Bi l_head = first;
        Bi l_tail = first;

        Bi r_head = last;
        Bi r_tail = last;

        while ( true )
         {
           // guarded to avoid overruns.
           //
           // @note this is necessary since ordered comparisons are
           // unavailable for bi-directional iterator types.
           while ( true )
              if (l_tail == r_head)
                 goto fixup_final;
              else if (comp(*l_tail, pivot))
                 ++l_tail;
              else
                 break;
           --r_head;
           while ( true )
              if (l_tail == r_head)
                 goto fixup_right;
              else if (comp(pivot, *r_head))
                 --r_head;
              else
                 break;

           std::iter_swap(l_tail, r_head);

           // compact equal to sequence front/back.
           if (!comp(*l_tail, pivot))
              std::iter_swap(l_tail, l_head++);
           if (!comp(pivot, *r_head))
              std::iter_swap(r_head, --r_tail);
           ++l_tail;
         }

fixup_right:
        // loop exited before chance to eval.
        if (!comp(pivot, *r_head))
           ++r_head;
fixup_final:
        // swap equal to partition point.
        if ((l_tail - l_head) <= (l_head - first))
           l_tail = std::swap_ranges(l_head, l_tail, first);
        else
           l_tail = swap_ranges_backward(first, l_head, l_tail);

        if ((r_tail - r_head) <= (last - r_tail))
           r_head = swap_ranges_backward(r_head, r_tail, last);
        else
           r_head = std::swap_ranges(r_tail, last, r_head);
        // equal range in values equal to pivot.
        return std::pair<Bi, Bi>(l_tail, r_head);
  }
虽然上述排序可能有效,但它说明了另一个答案已经指出的一点,即双向迭代器和快速排序不适合

由于无法在固定时间内选择合适的轴心,性能问题使快速排序成为了一个较差的选择。此外,双向迭代器过于通用,无法在链表上进行最佳排序,因为它们无法利用列表的优势,例如固定时间插入和拼接。最后,另一个更重要的子迭代器问题是,用户希望链表上的排序是稳定的

我的建议?sgi STL使用的自底向上的迭代合并排序。它经过验证、稳定、简单且快速(保证n*log(n))。不幸的是,该算法似乎没有唯一的名称,并且我无法单独找到到实现的链接,因此在此重复

这是一个非常巧妙的算法,它的工作方式类似于二进制计数器(即
template<typename Bi, typename Cmp>
  void qsort_bi(Bi first, Bi last, Cmp comp)
  {
        auto nmemb = std::distance(first, last);
        if (nmemb <= 1)
           return;
        Bi pivot = first;
        std::advance(pivot, std::rand() % nmemb);

        std::pair<Bi, Bi> equal = partition3(first, last, *pivot, comp);
        qsort_bi(first, equal.first, comp);
        qsort_bi(equal.second, last, comp);
  }

template<typename Bi>
  void qsort_bi(Bi first, Bi last)
  {
        typedef typename std::iterator_traits<Bi>::value_type value_type;
        qsort_bi(first, last, std::less<value_type>());
  }
template <typename Tp>
  void msort_list(std::list<Tp>& in)
  {
        std::list<Tp> carry;
        std::list<Tp> counter[64];
        int fill = 1;

        while (!in.empty()) {
            carry.splice(carry.begin(), in, in.begin());
            int i = 0;
            for (; !counter[i].empty(); i++) {
                // merge upwards for stability.
                counter[i].merge(carry);
                counter[i].swap(carry);
            }
            counter[i].swap(carry);
            if (i == fill) ++fill;
        }
        for (int i = 1; i < fill; i++)
            counter[i].merge(counter[i-1]);
        in.swap(counter[fill-1]);
  }