首页 > 编程语言 >常见的排序算法——快速排序

常见的排序算法——快速排序

时间:2024-06-02 21:32:51浏览次数:13  
标签:__ 元素 log int 常见 切分 算法 排序

本文记述了快速排序的基本思想和一份参考实现代码,并在说明了算法的性能后用随机数据进行了验证。

◆ 思想

基于分治思想的快速排序,使用切分函数找到一个切分位置,保证其左侧子范围内的所有元素都不大于切分位置的元素,右侧子范围内的所有元素都不小于切分位置的元素。然后用递归调用分别对两个子范围排序。

算法的核心是切分函数。先随机取用一个元素作为切分元素,然后从待排序范围的左端开始向右扫描直到找到一个大于等于它的元素,在从数组的右端向左扫描直到找到一个小于等于它的元素,交换这两个元素。如此反复,直到左右扫描相遇为止。最后将切分元素与相遇点元素交换,即完成切分。如要得到逆序的结果,则仅需改变比较的方向即可。

◆ 实现

排序代码采用《算法(第4版)》的“排序算法类模板”实现。(代码中涉及的基础类,如 Array,请参考算法文章中涉及的若干基础类的主要API

// quick.hxx

...

class Quick
{

    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    sort(Array<_T> & a)
    {
        Std_Random::shuffle(a);              // #1
        __sort__(a, 0, a.size()-1);
    }
    
    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    __sort__(Array<_T> & a, int lo, int hi)
    {
        if (hi <= lo) return;
        int j = __partition__(a, lo, hi);       // #2
        __sort__(a, lo, j-1);                // #3
        __sort__(a, j+1, hi);
    }

    ...

    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    __partition__(Array<_T> & a, int lo, int hi)
    {
        int i = lo, j = hi+1;
        _T v = a[lo];              // #4
        while (true) {
            while (__less__(a[++i], v)) if (i == hi) break;      // #5
            while (__less__(v, a[--j])) if (j == lo) break;      // #6
            if (i >= j) break;
            __exch__(a, i, j);
        }
        __exch__(a, lo, j);         // #7
        return j;
    }

    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    bool
    __less__(_T const& v, _T const& w)
    {
        return v.compare_to(w) < 0;         // #8
    }

    ...
    
    template
    <
        class _T,
        class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
    >
    static
    void
    __exch__(Array<_T> & a, int i, int j)
    {
        _T t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    
    ...

“快速排序最多需要约 N^2 / 2 次比较,但随机打乱数组能够预防这种情况。”(引《算法(第4版)》)(#1)。 使用切分函数找到一个切分位置(#2),将待排序元素分为左右两个待排序子范围,然后用递归调用分别对两个子范围分别排序(#3)。切分函数中,先用第一个元素作为切分元素(#4),然后从待排序范围的左端开始向右扫描直到找到一个大于等于它的元素(#5),再从数组的右端向左扫描直到找到一个小于等于它的元素(#6),交换这两个元素。如此继续,直到左右扫描相遇为止。最后将切分元素与相遇点元素交换(#7),即完成切分。将 '<' 改为 '>',即得到逆序的结果(#8)。

◆ 性能

时间复杂度 空间复杂度 是否稳定
N*log(N) log(N)

◆ 验证

测试代码采用《算法(第4版)》的倍率实验方案,用随机数据验证其正确性并获取时间复杂度数据。

// test.cpp
    
...

time_trial(int N)
{
    Array<Double> a(N);
    for (int i = 0; i < N; ++i) a[i] = Std_Random::random();    // #1
    Stopwatch timer;
    Quick::sort(a);                     // #2
    double time = timer.elapsed_time();
    assert(Quick::is_sorted(a));            // #3
    return time;
}

...

test(char * argv[])
{
    int T = std::stoi(argv[1]);          // #4
    double prev = time_trial(512);
    Std_Out::printf("%10s%10s%7s\n", "N", "Time", "Ratio");
    for (int i = 0, N = 1024; i < T; ++i, N += N) {            // #5
        double time = time_trial(N);
        Std_Out::printf("%10d%10.3f%7.2f\n", N, time, time/prev);   // #6
        prev = time;
    }
}

...

用 [0,1) 之间的实数初始化待排序数组(#1),打开计时器后执行排序(#2),确保得到正确的排序结果(#3)。整个测试过程要执行 T 次排序(#4)。每次执行排序的数据规模都会翻倍(#5),并以上一次排序的时间为基础计算倍率(#6),

此测试在实验环境一中完成,

$ g++ -std=c++11 test.cpp std_out.cpp std_random.cpp stopwatch.cpp type_wrappers.cpp

$ ./a.out 15

         N      Time  Ratio
      1024     0.007   2.33
      2048     0.015   2.14
      4096     0.031   2.07
      8192     0.066   2.13
     16384     0.137   2.08
     32768     0.293   2.14
     65536     0.609   2.08
    131072     1.293   2.12
    262144     2.690   2.08
    524288     5.526   2.05
   1048576    12.020   2.18
   2097152    24.061   2.00
   4194304    50.476   2.10
   8388608   103.608   2.05
  16777216   217.020   2.09

可以看出,随着数据规模的成倍增长,排序所花费的时间将是上一次规模的 2.1? 倍,且在不断波动地变小。将数据反映到以 2 为底数的对数坐标系中,可以得到如下图像,

test_data

O(N*log(N)) 代表了线性对数级别复杂度下的理论排序时间,该行中的数据是以 Time 行的第一个数据为基数逐一乘 2 + 2/log(N) 后得到的结果(因为做的是倍率实验,所以乘 (2*N*log(2*N)) / (N*log(N)),化简得到 2 + 2/log(N),即乘 2+2/log(1024),2+2/log(2048),2+2/log(4096),... 2+2/log(16777216);因为是二分递归,所以 log 的底数为 2)。

◆ 最后

完整的代码请参考 [gitee] cnblogs/18227388

写作过程中,笔者参考了《算法(第4版)》的快速排序、“排序算法类模板”和倍率实验。致作者 Sedgwick,Wayne 及译者谢路云。

标签:__,元素,log,int,常见,切分,算法,排序
From: https://www.cnblogs.com/green-cnblogs/p/18227388

相关文章

  • MD5加密算法中的加盐值(SALT)简单理解
    MD5是一种广泛使用的加密散列函数,它可以产生一个128位(16字节)的哈希值,通常用一个32位的十六进制字符串表示。MD5的主要目的是确保数据的完整性,而不是用于安全加密。加盐(Salting)是一种安全措施,用于增强密码存储的安全性。在密码学中,加盐值是一个随机生成的数据片段,它与密码结......
  • 八大排序:插入、希尔、冒泡、选择、快速、堆、归并、计数
    目录一、插入排序:二、希尔排序:三、冒泡排序:四、选择排序:五、快速排序:    1、霍尔法:    2、挖坑法:    3、前后指针法:    4、非递归的实现:六、堆排序:七、归并排序:1、递归实现:2、非递归实现:八、计数排序:一、插入排序:插入排序......
  • sift算法实现指纹验证
     本代码将使用SIFT特征检测和FLANN匹配器来比较两枚指纹是否与模板指纹匹配。模板指纹需要匹配的指纹1.定义显示图像的函数importcv2defcv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)2.定义计算匹配点个数的函数defverification(src,mode......
  • 堆排序-java
    这次主要讲了堆排序和堆的基本构造,下一期会详细讲述堆的各种基本操作。文章目录前言一、堆排序1.题目描述2.堆二、算法思路1.堆的存储2.结点下移down3.结点上移up4.堆的基本操作5.堆的初始化三、代码如下1.代码如下:2.读入数据:3.代码运行结果总结前言......
  • c语言基本概念和数据类型常见问题
    1.两种特殊的转义字符:\ddd和\xdd是什么?• \ddd :ddd表⽰1~3个⼋进制的数字。如: \130表⽰字符X• \xdd :dd表⽰2个⼗六进制数字。如:\x30表⽰字符02.指出里面哪些是转义字符,并给出运行结果printf("%zd\n",strlen("c:\\test\128\abcd.c"));转义字符有: \\ , \1......
  • js 一些常见新语法实践测试
    //生成多少个yield就能被forof遍历多少次function*mytest(){for(leti=0;i<5;i++){yieldMath.random(10)*1000}}//forof会迭代生成器里面所有的yield,有多少个yield就会迭代多少次for(letiofmytest()){console.log(i,"i"......
  • 代码随想录算法训练营第二十天 | 654.最大二叉树 617.合并二叉树 二叉搜索树
    654.最大二叉树题目链接文章讲解视频讲解classSolution{public:TreeNode*constructMaximumBinaryTree(vector<int>&nums){returninorderTraversal(nums,0,nums.size()-1);}TreeNode*inorderTraversal(vector<int>&nums,intbegi......
  • python自然语言处理实战:核心技术与算法 (涂铭,刘祥,刘树春)高清电子版pdf下载百度云
    书:pan.baidu.com/s/1rOoEvizAhkQyF8xScVh51w?pwd=8onw提取码:8onw我的阅读笔记:Python基础:为了进行NLP任务,首先需要掌握Python编程语言的基础知识。文本预处理:这包括文本清洗(如去除标点、停用词、特殊字符等)、文本分词(如中文分词)、文本向量化(如词袋模型、TF-IDF、Word2Vec等)。......
  • WHAT - 常见的前端工具库
    前端开发有许多工具库,可以帮助开发者提高效率、简化代码和增强功能。以下是一些常见的前端工具库:一、常见工具库函数库1.Lodash(推荐)Lodash是一个JavaScript实用程序库,提供了一系列方便的函数,用于数组、对象和其他数据类型的操作。网址:Lodash2.Underscore.js类似......
  • 囚徒5.3_SSA_BP算法
    麻雀算法加上bp网络importnumpyasnpimporttensorflowastffromtensorflow.keras.datasetsimportmnistfromtensorflow.keras.utilsimportto_categoricalfromtensorflow.keras.modelsimportModelfromtensorflow.keras.layersimportInput,Conv2D,MaxPoolin......