草庐IT

c++ - 有没有人见过这种对快速排序的改进?

coder 2023-05-03 原文

处理先前快速排序中的重复元素

我找到了一种在快速排序中更有效地处理重复元素的方法,并且想知道是否有人以前见过这样做过。

这种方法大大减少了检查重复元素所涉及的开销,从而提高了有和没有重复元素的性能。通常,重复元素以几种不同的方式处理,我将首先列举这些方式。

首先,有荷兰国旗方法对数组进行排序,如 [ < pivot | == pivot | unsorted | > pivot] .

其次,排序时将相等的元素放在最左边,然后将它们移动到中心的方法是[ == pivot | < pivot | unsorted | > pivot]然后在排序后 ==元素移动到中心。

第三,Bentley-McIlroy 分区把==两边的元素,所以排序是 [ == pivot | < pivot | unsorted | > pivot | == pivot]然后是 ==元素移动到中间。

最后两种方法是为了减少开销。

我的方法

现在,让我解释一下我的方法如何通过减少比较次数来改进快速排序。
我一起使用两个快速排序函数,而不是一个。

我将调用的第一个函数 q1并将数组排序为 [ < pivot | unsorted | >= pivot] .

我将调用的第二个函数 q2并将数组排序为 [ <= pivot | unsorted | > pivot] .

现在让我们看看这些串联的用法,以改进对重复元素的处理。

首先,我们调用q1对整个数组进行排序。它选择一个枢轴,我们将进一步将其称为 pivot1然后围绕 pivot1 进行排序.因此,我们的数组在这一点上被排序为 [ < pivot1 | >= pivot1 ] .

然后,对于 [ < pivot1]分区,我们将其发送至 q1再一次,那部分是相当正常的,所以让我们先对另一个分区进行排序。

对于 [ >= pivot1]分区,我们将其发送至 q2 . q2选择一个枢轴,我们将其称为 pivot2从此子数组中将其排序为 [ <= pivot2 | > pivot2] .

如果我们现在查看整个数组,我们的排序看起来像 [ < pivot1 | >= pivot1 and <= pivot2 | > pivot2] .这看起来非常像双枢轴快速排序。

现在,让我们回到 q2 内部的子数组( [ <= pivot2 | > pivot2] )。

对于 [ > pivot2]分区,我们只需将其发送回 q1这不是很有趣。

对于 [ <= pivot2]分区,我们首先检查是否pivot1 == pivot2 .如果它们相等,那么这个分区已经排序了,因为它们都是相等的元素!如果枢轴不相等,那么我们只需将此分区发送到 q2再次选择一个枢轴(进一步 pivot3 ),排序,如果 pivot3 == pivot1 ,则不必对 [ <= pivot 3] 进行排序等等。

希望你现在明白了。这种技术的改进是处理相等的元素,而不必检查每个元素是否也等于枢轴。换句话说,它使用较少的比较。

我还没有尝试过的另一种可能的改进是 checkin qs2如果[ <= pivot2]的大小与其总子数组的大小相比,分区相当大(或 [> pivot2] 分区非常小),然后在这种情况下对重复元素进行更标准的检查(上面列出的方法之一)。

源代码

这里有两个非常简化的 qs1qs2职能。他们使用 Sedgewick 收敛指针排序方法。他们显然可以非常优化(例如,他们选择的支点非常糟糕),但这只是为了展示这个想法。我自己的实现更长、更快、更难阅读,所以让我们从这个开始:

// qs sorts into [ < p | >= p ]
void qs1(int a[], long left, long right){
    // Pick a pivot and set up some indicies
    int pivot = a[right], temp;
    long i = left - 1, j = right;
    // do the sort
    for(;;){
        while(a[++i] < pivot);
        while(a[--j] >= pivot) if(i == j) break;
        if(i >= j) break;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    // Put the pivot in the correct spot
    temp = a[i];
    a[i] = a[right];
    a[right] = temp;

    // send the [ < p ] partition to qs1
    if(left < i - 1)
        qs1(a, left, i - 1);
    // send the [ >= p] partition to qs2
    if( right > i + 1)
        qs2(a, i + 1, right);
}

void qs2(int a[], long left, long right){
    // Pick a pivot and set up some indicies
    int pivot = a[left], temp;
    long i = left, j = right + 1;
    // do the sort
    for(;;){
        while(a[--j] > pivot);
        while(a[++i] <= pivot) if(i == j) break;
        if(i >= j) break;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    // Put the pivot in the correct spot
    temp = a[j];
    a[j] = a[left];
    a[left] = temp;

    // Send the [ > p ] partition to qs1
    if( right > j + 1)
        qs1(a, j + 1, right);
    // Here is where we check the pivots.
    // a[left-1] is the other pivot we need to compare with.
    // This handles the repeated elements.
    if(pivot != a[left-1])
            // since the pivots don't match, we pass [ <= p ] on to qs2
        if(left < j - 1)
            qs2(a, left, j - 1);
}

我知道这是一个相当简单的想法,但是当我添加标准的快速排序改进(3 个枢轴选择的中位数,以及小数组的插入排序作为开始)时,它在运行时提供了相当显着的改进。如果您打算使用此代码进行测试,请仅对随机数据进行测试,因为基准点选择不佳(或改进基准点选择)。要使用这种类型,您可以调用:
qs1(array,0,indexofendofarray);

一些基准

如果你想知道它有多快,这里有一些数据供初学者使用。这使用了我的优化版本,而不是上面给出的版本。然而,与 std::sort 相比,上面给出的那个在时间上仍然更接近于双枢轴快速排序。时间。

在具有 2,000,000 个元素的高度随机数据上,我得到这些时间(通过对几个连续数据集进行排序):
std::sort - 1.609 seconds  
dual-pivot quicksort - 1.25 seconds  
qs1/qs2 - 1.172 seconds

哪里std::sort是 C++ 标准库排序,双枢轴快速排序是几个月前由 Vladimir Yaroslavskiy 和 qs1/qs2 推出的排序。是我的快速排序实现。

在更少的随机数据上。具有 2,000,000 个元素并使用 rand() % 1000 生成(这意味着每个元素大约有 2000 个拷贝)时间是:
std::sort - 0.468 seconds  
dual-pivot quicksort - 0.438 seconds  
qs1/qs2 - 0.407 seconds

在某些情况下,双枢轴快速排序会胜出,我确实意识到双枢轴快速排序可以进行更多优化,但对于我的快速排序也可以安全地进行说明。

有没有人见过这个?

我知道这是一个很长的问题/解释,但是你们之前有没有见过这种改进?如果是这样,那为什么不使用它?

最佳答案

弗拉基米尔·雅罗斯拉夫斯基 | 9 月 11 日 12:35
用新的双枢轴快速排序替换 java.util.Arrays 中的快速排序

访问 http://permalink.gmane.org/gmane.comp.java.openjdk.core-libs.devel/2628

关于c++ - 有没有人见过这种对快速排序的改进?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2105737/

有关c++ - 有没有人见过这种对快速排序的改进?的更多相关文章

  1. ruby - 难道Lua没有和Ruby的method_missing相媲美的东西吗? - 2

    我好像记得Lua有类似Ruby的method_missing的东西。还是我记错了? 最佳答案 表的metatable的__index和__newindex可以用于与Ruby的method_missing相同的效果。 关于ruby-难道Lua没有和Ruby的method_missing相媲美的东西吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7732154/

  2. ruby-on-rails - rails 目前在重启后没有安装 - 2

    我有一个奇怪的问题:我在rvm上安装了ruby​​onrails。一切正常,我可以创建项目。但是在我输入“railsnew”时重新启动后,我有“程序'rails'当前未安装。”。SystemUbuntu12.04ruby-v"1.9.3p194"gemlistactionmailer(3.2.5)actionpack(3.2.5)activemodel(3.2.5)activerecord(3.2.5)activeresource(3.2.5)activesupport(3.2.5)arel(3.0.2)builder(3.0.0)bundler(1.1.4)coffee-rails(

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  5. 没有类的 Ruby 方法? - 2

    大家好!我想知道Ruby中未使用语法ClassName.method_name调用的方法是如何工作的。我头脑中的一些是puts、print、gets、chomp。可以在不使用点运算符的情况下调用这些方法。为什么是这样?他们来自哪里?我怎样才能看到这些方法的完整列表? 最佳答案 Kernel中的所有方法都可用于Object类的所有对象或从Object派生的任何类。您可以使用Kernel.instance_methods列出它们。 关于没有类的Ruby方法?,我们在StackOverflow

  6. ruby-on-rails - Rails 3,嵌套资源,没有路由匹配 [PUT] - 2

    我真的为这个而疯狂。我一直在搜索答案并尝试我找到的所有内容,包括相关问题和stackoverflow上的答案,但仍然无法正常工作。我正在使用嵌套资源,但无法使表单正常工作。我总是遇到错误,例如没有路线匹配[PUT]"/galleries/1/photos"表格在这里:/galleries/1/photos/1/edit路线.rbresources:galleriesdoresources:photosendresources:galleriesresources:photos照片Controller.rbdefnew@gallery=Gallery.find(params[:galle

  7. ruby-on-rails - 有没有办法为 CarrierWave/Fog 设置上传进度指示器? - 2

    我在Rails应用程序中使用CarrierWave/Fog将视频上传到AmazonS3。有没有办法判断上传的进度,让我可以显示上传进度如何? 最佳答案 CarrierWave和Fog本身没有这种功能;你需要一个前端uploader来显示进度。当我不得不解决这个问题时,我使用了jQueryfileupload因为我的堆栈中已经有jQuery。甚至还有apostonCarrierWaveintegration因此您只需按照那里的说明操作即可获得适用于您的应用的进度条。 关于ruby-on-r

  8. ruby - 没有类方法获取 Ruby 类名 - 2

    如何在Ruby中获取BasicObject实例的类名?例如,假设我有这个:classMyObjectSystem我怎样才能使这段代码成功?编辑:我发现Object的实例方法class被定义为returnrb_class_real(CLASS_OF(obj));。有什么方法可以从Ruby中使用它? 最佳答案 我花了一些时间研究irb并想出了这个:classBasicObjectdefclassklass=class这将为任何从BasicObject继承的对象提供一个#class您可以调用的方法。编辑评论中要求的进一步解释:假设你有对象

  9. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  10. ruby - 没有轨道的 ActiveRecord 时区 - 2

    我在非Rails项目中使用ActiveRecord。在Rails中,我可以这样做:config.time_zone='EasternTime(US&Canada)'config.active_record.default_timezone='EasternTime(US&Canada)'但如果我不使用rails,我该如何设置时区? 最佳答案 ActiveRecord::Base.default_timezone='EasternTime(US&Canada)' 关于ruby-没有轨道的A

随机推荐