• 双路快速排序法


    1、算法出现的背景

    之前讲的,当我们排序的是一个近乎有序的序列时,快速排序会退化到一个O(n^2)级别的排序算法,

    而对此的改进就是引入了随机化快速排序算法;但是当我们排序的是一个数值重复率非常高的序列时,

    此时随机化快速排序算法就不再起作用了,而将会再次退化为一个O(n^2)级别的排序算法,那为什么

    会出现这种情况呢?且听下面的分析:

    如上图所示就是之前分析的快速排序算法的partition的操作原理,我们通过判断此时i索引指向的数组

    元素e>v还是<v,将他放在橙色或者是紫色两个不同的位置,然后将整个数组分成两个部分递归下去;

    但是这里其实我们是没有考虑=v的情况,其实隐含的意思就是下面的两种情况:

             

    其实从这里就可以看出来了,不管是>=v还是<=v,当我们的序列中存在大量重复的元素时,

    排序完成之后就会将整个数组序列分成两个极度不平衡的部分,所以又退化到了O(n^2)级别

    的时间复杂度,这是因为对于每一个"基准"元素来说,重复的元素太多了,如果我们选的"基准"

    元素稍微有一点的不平衡,那么就会导致两部分的差距非常大;即时我们的"基准"元素选在了

    一个平衡的位置,但是由于等于"基准"元素的元素也非常多,也会使得序列被分成两个及其不平

    衡的部分,那么在这种情况下快速排序就又会退化成O(n^2)级别的排序算法。如何解决呢?

    这就要用到今天讲的双路快速排序算法的原理了。

    2、双路快速排序算法的原理

    之前说的快速排序算法是将>v和<v两个部分元素都放在索引值i所指向的位置的左边部分,而我们

    的双路快速排序算法则不同,他使用两个索引值(i、j)用来遍历我们的序列,将<v的元素放在索

    引i所指向位置的左边,而将>v的元素放在索引j所指向位置的右边,这也正是双路排序算法的

    partition原理:

    基本思想: 

     首先从左边的i索引往右边遍历,如果i指向的元素<v,

                                                                    那直接将i++移动到下一个位置,直道i指向的元素>=v则停止

     然后使用j索引从右边开始往左边遍历,如果j指向的元素>v,

                                                                    那直接将j--移动到下一个位置,直道j指向的元素<=v则停止

     此时i之前的元素都已经归并为<v的部分了,而j之后的元素也

                                                                   都已经归并为>v的部分了,此时只需要将arr[i]和arr[j]交换位置即可

     这样就可以避免出现=v的元素全部集中在某一个部分,这正

                                                                   是双路排序算法的一个核心

      将i++,j--开始遍历后后面的元素

    3、双路快速排序算法的实现(基于C++)

     1 /******************************** 双路快速排序算法实现 ***************************************/
     2 template<typename T>
     3 T __partition2 (T arr[], int left, int right)
     4 {
     5     std::swap(arr[left], arr[std::rand() % (right - left + 1) + left]);   // 随机产生"基准"元素所在位置,并与第一个元素交换位置
     6     T v = arr[left];                       // 将第一个元素作为"基准"元素
     7 
     8     // 使用i索引从左到右遍历,使用j索引从右到左遍历
     9     int i = left + 1;       // 索引值i初始化为第二个元素位置
    10     int j = right;          // 索引值j初始化为最后一个元素位置
    11     while (true) {
    12         while ((i < right) && (arr[i] < v)) i++;    // 使用索引i从左往右遍历直到 arr[i] < v
    13         while ((j > left + 1) && (arr[j] > v)) j--; // 使用索引j从右往左遍历直到 arr[j] > v
    14         if (i >= j) break;                          // 退出循环的条件
    15         std::swap(arr[i], arr[j]);                  // 将 arr[i] 与 arr[j] 交换位置
    16         i++;                                        // i++    j--
    17         j--;
    18     }
    19 
    20     std::swap(arr[left], arr[j]);             // 最后将"基准"元素v放置到合适的位置
    21 
    22     return j;
    23 }
    24 
    25 template<typename T>
    26 void __quickSort2 (T arr[], int left, int right)
    27 {
    28     if (right - left <= 40) {           // 当递归到序列数据量较小时使用插入排序算法
    29         __insertSortMG<T>(arr, left, right);
    30         return;
    31     }
    32 
    33     int p = __partition2<T>(arr, left, right);  // 对arr[left...right]区间元素进行partition操作,找到"基准"元素
    34     __quickSort2<T>(arr, left, p - 1);          // 对基准元素之前的序列递归调用__quickSort函数
    35     __quickSort2<T>(arr, p + 1, right);         // 对基准元素之后的序列递归调用__quickSort函数
    36 }
    37 
    38 template<typename T> 
    39 void quickSort2 (T arr[], int count)
    40 {
    41     std::srand(std::time(NULL));         // 种下随机种子
    42     __quickSort2<T>(arr, 0, count - 1);  // 调用__quickSort函数进行快速排序
    43 }
    44 /*********************************************************************************************/

    4、性能测试

    一般序列:

    近乎有序的序列:

    重复率非常高的序列:

  • 相关阅读:
    (转)java web自定义分页标签
    关于在springmvc下使用@RequestBody报http status 415的错误解决办法
    (转)解决点击a标签返回页面顶部的问题
    优先队列详解priority_queue .RP
    7.23 学习问题
    7.24 学习问题
    7.25 学习问题
    python装饰器学习笔记
    SQL数据库简单操作
    form属性method="get/post
  • 原文地址:https://www.cnblogs.com/deng-tao/p/6523433.html
Copyright © 2020-2023  润新知