使用下面的分区的quickSort方法有什么问题?第N个元素似乎工作正常,但我认为分区也可以。我看到一个有2个分区调用的例子,但我不应该只需要一个?
#include <iostream>
#include <algorithm>
#include <iterator>
template <typename It>
void quickSortWorks (const It& lowerIt, const It& upperIt)
{
auto d = upperIt - lowerIt ;
if ( d < 2 )
return;
auto midIt = lowerIt + d / 2;
std::nth_element ( lowerIt, midIt, upperIt);
quickSortWorks( lowerIt, midIt );
quickSortWorks( midIt+1, upperIt );
}
template <typename It>
void quickSort (const It& lowerIt, const It& upperIt)
{
auto d = upperIt - lowerIt ;
if ( d < 2 )
return;
auto midIt = lowerIt + d / 2;
auto pIt = std::partition ( lowerIt, upperIt, [midIt](int i) { return i < *midIt; } );
quickSort( lowerIt, pIt );
quickSort( pIt + 1, upperIt );
}
int main ( )
{
unsigned int N = 10;
std::vector<int> v(N);
// srand (time(nullptr));
for_each(v.begin(), v.end(), [](int& cur){ cur = rand()%100; });
std::vector<int> vorig(v);
auto print_vec = [](std::ostream& out, const std::vector<int>& v) {
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(out, ", "));
out << std::endl;
};
std::cout << " Using Partition: " << std::endl;
std::cout << " Before: " << std::endl;
print_vec(std::cout,v);
quickSort(v.begin(), v.end());
std::cout << " After: " << std::endl;
print_vec(std::cout,v);
v = vorig;
std::cout << " Using Nth Element: " << std::endl;
std::cout << " Before: " << std::endl;
print_vec(std::cout,v);
quickSortWorks(v.begin(), v.end());
std::cout << " After: " << std::endl;
print_vec(std::cout,v);
}
输出:
Using Partition:
Before:
83, 86, 77, 15, 93, 35, 86, 92, 49, 21,
After:
21, 15, 77, 35, 49, 83, 86, 92, 86, 93,
Using Nth Element:
Before:
83, 86, 77, 15, 93, 35, 86, 92, 49, 21,
After:
15, 21, 35, 49, 77, 83, 86, 86, 92, 93,
答案 0 :(得分:4)
编写的代码只是偶然发生,这就是原因
std::partition
通过谓词完成其工作,正向序列包含评估为true的元素,其余值计算为false。这意味着std::partition
视图大于枢轴的元素和等于枢轴的元素等效。
无法保证订购序列[middle,last)
!
显然这不是你想要的。您希望将等于pivot的元素压缩到序列[middle,last)
的前面。这就是为什么你看过的示例代码使用了第二个分区,在尾随序列上强加了这个顺序(最低限度你需要在正确的位置使用pivot元素)。
为了清楚起见,
template<typename Ran>
void quicksort(Ran first, Ran last)
{
typedef typename std::iterator_traits<Ran>::value_type value_type;
if (last - first < 2)
return;
Ran middle = first + (last - first)/2;
// save pivot.
std::iter_swap(middle, last-1);
middle = std::partition(first, last-1,
[last] (const value_type& x) { return x < *(last-1); });
// restore pivot.
std::iter_swap(middle, last-1);
quicksort(first, middle);
quicksort(middle+1, last);
}
答案 1 :(得分:3)
即使使用lambda修复也无法工作,因为与std::partition
不同,std::nth_element
不会返回适合分而治之的递归的迭代器。
对std::partition
的调用的返回值是分区“上限”范围内 first 值的迭代器,其中谓词失败。除非意外,否则这将不是该范围内“最小”的迭代器。
相比之下,std::nth_element
中的“旋转”操作恰好达到了这一点,这对于分叉递归也是必要的。
通过手工处理第一次迭代的例子可以看出失败。这个测试序列包含10个元素:
83, 86, 77, 15, 93, 35, 86, 92, 49, 21
第一个“pivot”将是第6个元素(在索引0 + 10/2 = 5),即35.使用标准“小于35”std::partition
将在第一步重新排列数组
21, 15, 77, 86, 93, 35, 86, 92, 49, 83
并返回指向第3个元素(= 77)的指针。显然,值77将在算法持续时间内保持在第三位置。这显然是错误的。
答案 2 :(得分:1)
你的闭包应该捕获* midIt的值,而不是midIt:在分区过程中数量“* midIt”会改变。
int midValue = *midIt;
std::partition(lowerIt, upperIt, [midValue](int i)...
答案 3 :(得分:0)
我只是遇到了同样的问题。花了数小时进行分析之后,终于找到了解决方案,瞧!!
正如已经提出的谓词std::partition
所述,该数组将分为两部分,第一部分由小于枢轴的元素组成,第二部分由大于或等于枢轴的元素组成。但是,不能保证枢轴将成为第二部分的第一个元素。
好吧,std::stable_partition
做完了。只需将枢轴作为first element
并应用stable_partition。因为它将在分区时保持发生的顺序。现在确保枢轴将成为第二部分的第一个元素。
PS:不要混淆两个部分。我用这个词来更清楚地解释事情。
template <typename It>
void quickSort (const It& lowerIt, const It& upperIt)
{
auto d = upperIt - lowerIt ;
if ( d < 2 )
return;
auto pIt = lowerIt;
auto pValue = *pIt;
pIt = std::stable_partition ( lowerIt, upperIt, [pValue](int i) { return i < pValue; } );
quickSort( lowerIt, pIt );
quickSort( pIt + 1, upperIt );
}