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

常见的排序算法——快速排序(二)

时间:2024-06-05 23:15:38浏览次数:21  
标签:__ log int lo 常见 算法 hi 排序

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

◆ 思想

本文实现了《算法(第4版)》书中提到的 2 项改进,

  • 切换到插入排序:对小规模子数组使用插入排序。减少在小规模数组中的递归调用能改进整个算法。
  • 三取样切分:将取样大小设为 3 并用大小居中的元素切分;同时因为中位数作为了切分元素,可以去掉数组边界测试。

◆ 实现

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

// quick2.hxx

...

class Quick2
{

    ...
    
    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);
        __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 <= 15) {                            // #1
            for (int i = lo+1; i <= hi; ++i) {
                _T t = a[i];
                int j;
                for (j = i; j > 0 && __less__(t, a[j-1]); --j)
                    a[j] = a[j-1];
                a[j] = t;
            }
        } else {
            int j = __partition__(a, lo, hi);
            __sort__(a, lo, j-1);
            __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 mi = lo + (hi - lo) / 2;
        if (__less__(a[mi], a[lo])) __exch__(a, mi, lo);           // #2
        if (__less__(a[hi], a[lo])) __exch__(a, hi, lo);
        if (__less__(a[hi], a[mi])) __exch__(a, hi, mi);
        __exch__(a, mi, lo);                               // #3
        int i = lo, j = hi+1;
        _T v = a[lo];                               // #3
        while (true) {
            while (__less__(a[++i], v));               // #4
            while (__less__(v, a[--j]));
            if (i >= j) break;
            __exch__(a, i, j);
        }
        __exch__(a, lo, j);
        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;
    }

    ...
    
    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;
    }
    
    ...

当待排序的子数组规模较小时,采用无交换的插入排序(#1)。对 a[lo], a[mi], a[hi] 三取样的同时保证它们的有序性(#2),并以中位数作为切分元素(#3),可以去掉数组边界测试(#4)。

◆ 性能

时间复杂度 空间复杂度 是否稳定
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;
    Quick2::sort(a);                     // #2
    double time = timer.elapsed_time();
    assert(Quick2::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.065   2.10
     16384     0.136   2.09
     32768     0.291   2.14
     65536     0.600   2.06
    131072     1.263   2.10
    262144     2.625   2.08
    524288     5.410   2.06
   1048576    11.274   2.08
   2097152    23.266   2.06
   4194304    48.453   2.08
   8388608    99.481   2.05
  16777216   206.395   2.07

可以看出,随着数据规模的成倍增长,排序所花费的时间将是上一次规模的 2.0? 倍,且在不断波动地变小。将数据反映到以 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/18233834

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

标签:__,log,int,lo,常见,算法,hi,排序
From: https://www.cnblogs.com/green-cnblogs/p/18233834

相关文章

  • 【面试宝藏】Redis 常见面试题解析其二
    Redis高级面试题解析20.说说Redis哈希槽的机制?Redis集群采用哈希槽(HashSlot)机制来分布和管理数据。整个哈希空间被划分为16384个槽,每个键通过CRC16校验后取模映射到一个哈希槽。每个节点负责一部分哈希槽,从而实现数据分片和负载均衡。21.Redis集群的主从复制......
  • 算法金 | 10 大必知的自动化机器学习库(Python)
    大侠幸会,在下全网同名[算法金]0基础转AI上岸,多个算法赛Top[日更万日,让更多人享受智能乐趣]一、入门级自动化机器学习库1.1Auto-Sklearn简介:Auto-Sklearn是一个自动机器学习库,基于Python的scikit-learn接口。它主要用于自动化机器学习的常见过程,特别是算法选......
  • 代码随想录算法训练营第29天 | 491.递增子序列 、46.全排列 、47.全排列 II
    491.递增子序列本题和大家刚做过的90.子集II非常像,但又很不一样,很容易掉坑里。https://programmercarl.com/0491.递增子序列.html视频讲解:https://www.bilibili.com/video/BV1EG4y1h78v关键点还要在于本层使用过的数字不能再使用/***@param{number[]}nums*@return......
  • 每天五分钟计算机视觉:基于KNN算法完成图片分类任务
    本文重点在数字化和智能化的时代,图片分类作为计算机视觉领域的重要任务之一,已经广泛应用于各种场景,如安防监控、医疗诊断、智能推荐等。传统的图片分类方法往往需要复杂的手工特征提取和繁琐的分类器设计,而机器学习算法的引入为图片分类带来了不同的思路。KNN算法概述KNN算......
  • Studying-代码随想录算法训练营day1| 数组理论基础,704二分查找,27.移除元素
    第一天......
  • 代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素
    题目链接:https://leetcode.cn/problems/binary-search/描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。示例1:输入:nums=[-1,0,3,5,9,12],target=9输出:4解释:......
  • [职业教育核心管理平台]技术白皮-常见任务的PC端操作方法
    A.授课教师角色常见任务PC端操作:1.所授课程教材信息录入:2.授课班级信息的查询3.授课班级学生信息查询4.授课日志现场操作操作时间点:5.导出授课日志记录操作时间点:6.上课日志模板准备操作时间点:7.所授课程分数录入操作时间点:6.所授课程分数查询  B.班主任......
  • 基于毫米波雷达的手势识别算法
    摘要基于毫米波的手势识别技术提供了良好的人机交互体验。先前的工作专注于近距离手势识别,但在范围扩展方面不够,即他们无法识别距离相当大的噪声运动超过一米的手势。在本文中,我们利用一种新的数据处理方法和定制的人工卷积神经网络(CNN)设计了一个远程手势识别模型。首先,我们将手......
  • C语言排序
    一、排序的运用生活中排序随处可见,比如我们高考时的排名,大学学校水平的排名等,打开京东,可以发现每样商品按照不同的方式排序,比如综合,销量,价格。其内部需要排序代码来完成。二、常见的排序算法一、交换排序一、冒泡排序冒泡排序是一种最容易想到的排序,但是其效率不高,没有实......
  • 代码随想录算法训练营 第一天 704 二分查找 27 移除元素
    leetcode704 二分查找704二分查找思想:二分法简单二分问题注意二分问题有很多模式,二分问题查找核心是区间问题注意所学两种写法:区间左闭右开  区间左闭右闭二分查找问题 classSolution{publicintsearch(int[]nums,inttarget){if(target>nu......