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