std :: partition快速排序实现

时间:2013-10-08 20:16:01

标签: c++ c++11 quicksort

我认为我在下面的实施工作但显然没有。关于使用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,

2 个答案:

答案 0 :(得分:7)

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

  • 选择一个支点元素
  • 使用*std::prev(upperIt)
  • 交换枢轴元素
  • std::partition
  • 范围内使用[lowerIt, std::prev(upperIt))
  • 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);
    }
}

答案 1 :(得分:1)

您的代码存在一些严重问题。让我们分别考虑一下。

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

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

其次,就像几乎所有使用迭代器的东西一样,std::partition假定半开范围 - 即第一个迭代器指向范围的开头,第二个迭代器点刚刚过去范围的结束。这意味着您希望递归调用为:

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

理论上,跳过枢轴元素实际上并没有伤害任何东西(并且可以用来防止以前的问题)但是当你递归时将一个项目从处理中移除是非常不寻常的,我一般< / em>避免它。为了避免无限递归,你必须将你的数据库交换到上层分区的第一个位置,这样你就可以省去你在递归调用中传递的那个。如果你遗漏了其他一些元素,那么你就会遇到问题,因为你不会对所有元素进行排序。

对于Quicksort的“严肃”实现,还有一些您可能想要更改的其他细节,例如当分区大小减少到20个项目时停止递归,然后你这样做了,对整个系列进行插入排序,把所有东西都放到最后的休息处(可以这么说)。

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