使用带快速排序的std :: partition

时间:2013-09-28 23:32:17

标签: c++ algorithm

使用下面的分区的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, 

4 个答案:

答案 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 );
}