std::partition quick sort implementation

std::partition quick sort implementation

本文关键字:implementation sort partition std quick      更新时间:2023-10-16

我认为我在下面的实现是有效的,但显然没有。关于使用std::partition的这种快速排序实现有什么问题的任何想法?我有一个使用nth_element版本的版本,其代码与此非常相似且更简单。

template <typename It>
void quickSort (const It& lowerIt, const It& upperIt)
{
  auto d = upperIt  - lowerIt ;
  if ( d < 2 )
   return;
  auto midIt = lowerIt + d / 2;
  using T = typename std::iterator_traits<It>::value_type;
  T midValue = *midIt;
  auto pIt = std::partition ( lowerIt, upperIt, [midValue](T i) { return i < midValue; } );
  quickSort( lowerIt, pIt );
  quickSort( pIt + 1, upperIt );
}

使用分区:

以前:

83, 86, 77, 15, 93, 35, 86, 92, 49, 21,

后:

21, 15, 77, 35, 49, 83, 86, 92, 86, 93,

不能保证枢轴元素将位于位置 pIt 。在大多数情况下,它不会。因此,您应该按如下方式更改算法:

  • 选择透视元素
  • 枢轴元素与*std::prev(upperIt)交换
  • 在范围[lowerIt, std::prev(upperIt))上使用std::partition
  • pIt*std::prev(upperIt)交换
  • 像在代码中一样递归调用快速排序

下面是代码的固定版本:

template <typename It>
void quickSort(It lowerIt, It upperIt)
{
    using std::swap;
    auto size = std::distance(lowerIt, upperIt);
    if (size > 1) {
        auto p = std::prev(upperIt);
        swap(*std::next(lowerIt, size / 2), *p);
        auto q = std::partition(lowerIt, p, [p](decltype(*p) v) { return v < *p; });
        swap(*q, *p);
        quickSort(lowerIt, q);
        quickSort(std::next(q), upperIt);
    }
}

你的代码有几个严重的问题。让我们单独考虑它们。

首先,如果你选择作为枢轴的项恰好是集合中最小的,你会得到无限递归——它会把所有的元素都放到上分区中,但是当它尝试对上层分区进行排序时,它会以相同的顺序获得相同的元素,将它们全部放在上层分区中并无限重复。

消除这种情况的最常见方法是使用三个枢轴选择的中位数。这(几乎(保证至少有一个项目比透视小,一个项目比透视大,所以即使在最坏的情况下,你也会在每个分区中至少放置一个项目。唯一的例外是,如果您选择的三个项目都相同(在这种情况下,您通常需要/想要重新选择您的透视值(。

其次,像几乎所有使用迭代器的东西一样,std::partition假设一个半开放范围——即,第一个迭代器指向范围的开头,第二个迭代器指向范围的末尾这意味着您希望递归调用是:

quicksort(lowerIt, pIt);
quicksort(pIt, upperIt);

从理论上讲,跳过枢轴元素实际上不会造成任何伤害(并且可用于防止以前的问题(,但是在递归时将项目排除在处理之外是非常不寻常的,我通常会避免它。为了防止无限递归,您必须将枢轴交换到上部分区中的第一个位置,因此它是您在递归调用中传递的内容中遗漏的那个。如果你省略了一些其他元素,你会遇到问题,因为你不会对所有元素进行排序。

对于 Quicksort 的"严肃"实现,您可能还更改其他一些细节,例如在分区大小减少到 20 个项目时停止递归,然后以这种方式完成时,对整个集合进行插入排序,以将所有内容放到其最终的休息位置(可以这么说(。

同样,为了避免堆栈溢出,您通常希望先对两个分区中较小的分区进行排序。这可确保堆栈空间永远不会超过 O(log N(。就目前而言,堆栈空间可能是O(N(最坏的情况。