【问题标题】:Quicksort 3 way partition too slowQuicksort 3方式分区太慢
【发布时间】:2017-04-24 08:17:02
【问题描述】:

我正在使用快速排序 3 路分区,但是当向量大小大于 10000 时,它变得太慢了。 我究竟做错了什么?请指导我!任何帮助将不胜感激 答案应该在 2.2 秒内计算出来。

#include <iostream>
#include <vector>
#include <cstdlib>
#include <algorithm>

using std::vector;
using std::swap;

void print(vector<int> v)
{
  for(int i = 0; i < v.size(); i++) std::cout << v[i] << " ";
  std::cout << std::endl;
}

void partition2(vector<int> &a, int l, int r, int &i, int &j) {
  int k;
  int middle=(l+r)/2;
  /*Selecting pivot as median of low, high and middle*/
  if(((a[l]<=a[middle]) && (a[middle]<=a[r])) || ((a[r]<=a[middle]) && (a[middle]<=a[l])))
      k=middle;
  else if(((a[middle]<=a[l]) && (a[l]<=a[r])) || ((a[r]<=a[l]) && (a[l]<=a[middle])))
      k=l;
  else if(((a[middle]<=a[r]) && (a[r]<=a[l])) || ((a[l]<=a[r]) && (a[r]<=a[middle])))
      k=r;

  swap(a[l], a[k]);
  //print(a);

  int low_value = a[l];
  int index_low = l;
  int index_high = l;
  int counter=l;
  for (int i = l + 1; i <= r; i++) {
    if (a[i] < low_value) {
      swap(a[i], a[index_low]);
      counter++;
      low_value=a[l];
    }
    else if(a[i]==low_value)
    {
        index_high++;
        swap(a[i], a[index_high]);      
    }
    //print(a);
  }

  i=counter;
  j=index_high;
  //swap(a[l], a[j]);
  //return j;
}

void randomized_quick_sort(vector<int> &a, int l, int r) {
  if (l >= r) {
    return;
  }

  int i,j;
  partition2(a, l, r, i, j);

  randomized_quick_sort(a, l, i-1);
  randomized_quick_sort(a, j+1, r);
}

int main() {
  int n;
  std::cin >> n;
  //while(1){
  //n=100+rand()%99999;
  //std::cout<<n<<std::endl;
  vector<int> a(n);
  for (size_t i = 0; i < a.size(); ++i) {
    std::cin >> a[i];
    //a[i]=1+rand()%99999999;
  }
  randomized_quick_sort(a, 0, a.size() - 1);
  for (size_t i = 0; i < a.size(); ++i) {
    std::cout << a[i] << ' ';
  }
  //std::cout<<"Pass\n";  
  //}
  return 0;
}

【问题讨论】:

  • 你如何进行基准测试?当输入大小> 10000 时,您确定 main 中的 random_quick_sort 调用需要 2.2 秒吗?还是整个程序需要 2.2 秒?您是否使用 -O2 之类的标志进行编译?
  • @PetarPetrovic 当输入大小在 10000 到 100000 之间时,时差非常明显。我在 Coursera 自己的平台上进行了测试,它们指示了时间。是的,我正在使用 -O2 标志进行编译
  • 我怀疑问题出在 partition2() 上。我添加以下代码以查看枢轴位置 partition2(a, l, r, i, j); if (r - l > 1000){ std::cout

标签: c++ vector time quicksort


【解决方案1】:

乍一看,一切都是正确的。但是,可能有太多的比较操作。试试这个选项 - 它在我的电脑上平均工作 1.6 秒。

#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <ctime>
#include <random>
#include <chrono>
#include <iomanip>

using namespace std;
using namespace std::chrono;

//======= quick_sort =======//
template<typename T>
int partition(vector<T>& numbers, const int& left, const int& right)
{
    swap(numbers[left], numbers[left + (right - left) / 2]);
    T mid = numbers[left];
    int i(left + 1), j(right);

    while (i <= j)
    {
        while ( i <= j && numbers[i] <= mid ) i++;
        while ( i <= j && numbers[j] > mid ) j--;
        if ( i < j ) swap(numbers[i], numbers[j]);
    }

    swap(numbers[i - 1], numbers[left]);
    return i - 1;
}

template<typename T>
void quick_sort_rec(vector<T>& numbers, const int& left, const int& right)
{
    if (left >= right) return;

    int p = partition(numbers, left, right);
    quick_sort_rec(numbers, left , p - 1);
    quick_sort_rec(numbers, p + 1 , right);
}
//=========================//


template<typename T>
T random_T(long min, long max)
{
    return (T)min + static_cast<T>(rand()) / (static_cast<T>(RAND_MAX / ((T)(max - min))));
}

template<typename T>
float time_func(void (*f)(vector<T>&, const int&, const int&), vector<T>& a)
{
    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    f(a, 0, a.size() - 1);
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    return 1000.0 * (duration_cast<microseconds>(t2 - t1).count()) / (float)(CLOCKS_PER_SEC); /// CLOCKS_PER_SEC;
}

int main()
{
    srand((unsigned)(777));
    vector<int> a;

    for (int i = 0; i < 10000; i++)
    {
        a.push_back(random_T<int>(0, 1000));
    }

    cout << setprecision(10) << "quick sort rec = " << time_func(quick_sort_rec, a) << endl;
    return 0;
}

【讨论】:

  • 感谢您的代码,但如果我知道我在哪里花费了这么多时间以及如何减少它而不是仅仅复制您的解决方案,那将会更有帮助。
  • 正如我所说,瓶颈是您在数组中选择枢轴元素的方式。快速排序的质量很大程度上取决于这一刻。但是 - 你总是可以在没有递归的情况下实现排序,它可以稍微减少时间。例如,请参阅this 问题以获取更多信息。
  • P.S.:不幸的是,减少 Nlog_{3}(N) 时间的愿望面临着额外的开销。并且最有可能使用三个子数组比使用两个更快地实现排序 - 将不起作用。至少这并不容易。所以这里有必要考虑一下——你想要令人难以置信的优化或可靠性。
【解决方案2】:

我运行下面的代码来测试partition2

int main(){
    vector<int> a = {2, 1, 1, 9, 5, 3, 4, 2, 7};
    int i, j;
    partition2(a, 0, a.size() - 1, i, j);
    for (auto i : a)
        cout << i << ' ';
    cout << '\n';
    return 0;
}

结果是

1 1 5 9 2 3 4 2 7 

如果partition2选择low、high和middle的中位数作为pivot,那么pivot应该是5,结果应该是这样的

2 1 1 3 4 2 5 9 7 

然后我检查代码

if (a[i] < low_value) {
  swap(a[i], a[index_low]);
  counter++;
  low_value=a[l];
}
else if(a[i]==low_value)
{
    index_high++;
    swap(a[i], a[index_high]);      
}

似乎代码试图找到数组的最小值,然后将它们移动到数组的开头。它似乎在做选择排序而不是快速排序。它解释了为什么当输入大小很大时它会很慢。

【讨论】:

  • 我不确定您运行的是哪个代码,但我的代码运行得非常好并且显示正确的结果。如果您取消注释打印语句,您会得到以下结果:1 1 2 2 3 4 5 7 9 甚至枢轴都是正确的,即 5。就这一点而言,“似乎代码试图找到最小值数组,然后将它们移动到数组的开头。”担心,每次分区后数据集减少一半
  • @darkfall94 我更新了我的回答。基本上我只是在 main 中调用了一次 partition2 来测试这个函数。如果你只是复制我的 main 并运行它,你有同样的结果吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
  • 2016-02-12
相关资源
最近更新 更多