Reputation: 3336
What's wrong with the quickSort method using partition below? Nth element seems to work fine but I thought partition would also work. I saw an example that had 2 partition calls but shouldn't I just need one?
#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);
}
Output:
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,
Upvotes: 2
Views: 3366
Reputation: 11
I just gone through the same problem. After spending hours on analyzing, at last I came to the solution, Voila!!
As already mentioned std::partition
with proposed predicate will segregate the array in two parts, first part consists of elements less than pivot and second part, elements greater than or equal to the pivot. But it is not guaranteed that pivot will be the first element in the second part.
Well, std::stable_partition
does the job. Just take the pivot as first element
and apply the stable_partition. Since it will maintain the order of occurrence while partitioning. It is now guaranteed that pivot will be the first element in second part.
PS: Don't get confused with two parts. I used the term to explain things more clearly.
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 );
}
Upvotes: 1
Reputation: 81
The code as written only works accidentally and here's why,
std::partition
does its job by predicate, the forward sequence contains elements that evaluate to true, the remainder evaluate to false. This means that std::partition
views elements that are greater than the pivot and elements that are equal to the pivot as equivalent.
There is no guarantee on ordering for the sequence [middle,last)
!
Obviously this is not what you want. You would like to compact elements equal to pivot to the front of the sequence [middle,last)
. This is why the example code you had looked at used a second partition, to impose this ordering on the trailing sequence (minimally you'll need the pivot element in correct position).
For the sake of clarity,
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);
}
Upvotes: 8
Reputation: 2554
It won't work, even with the lambda fix, because std::partition
, unlike std::nth_element
, doesn't return an iterator suitable for the divide-and-conquer recursion.
The return value of a call to std::partition
is the iterator to the first value in the "upper" range of the partition, where the predicate has failed. Except by accident, this will not be an iterator to the "smallest" in that range.
By contrast, the "pivoting" operation in std::nth_element
achieves exactly that, which is also necessary for the bifurcating recursion.
The failure can be seen by working the example by hand for just the first iteration. With this test sequence of 10 elements:
83, 86, 77, 15, 93, 35, 86, 92, 49, 21
The first "pivot" will be the 6th element (at index 0 + 10/2 = 5), i.e. 35. Using the criterion "less than 35" std::partition
will rearrange the array at the first step to
21, 15, 77, 86, 93, 35, 86, 92, 49, 83
and return a pointer to the 3rd element (=77). It should be obvious that the value 77 will remain at that third position for the duration of the algorithm. And this is clearly wrong.
Upvotes: 5
Reputation: 1690
Your closure should capture the value of *midIt, rather than midIt: the quantity "*midIt" will change during partitioning.
int midValue = *midIt;
std::partition(lowerIt, upperIt, [midValue](int i)...
Upvotes: 0