【数据结构与算法】快速排序的优化

中心比较值优化、小规模直接插入排序优化、并行优化


前言

快速排序是一种比较优秀的排序算法,然而从快排的原理下手,我们能够对快速排序进一步优化。可以从中心值的选取、小规模直接插入排序、并行优化处理。

中心值优化:通过在样本(前中后)中选取中间值,来尽可能地选择离中位数最近的基准值。

小规模排序:待排序序列较小时,可以通过转变为例如直接插入排序来进行优化,避免深度递归。

并行优化:依据快速排序特点,子序列进行并行快速排序来优化提速


一、优化思路

1.1 中心值选取优化

        快速排序算法的原理,是选取一个中心值,将比它大/小的元素归为一边,一轮即可确定一个元素的位置。最坏情况下,对n个元素分类,可以分为n-1与1,算法复杂度O(n2)。最优情况下,对n个元素分类,可以分为n/2与n/2,或者(n-1)/2与(n-1)/2+1。算法复杂度为O(nlogn)。可见,中心值的选取对于算法的性能提升是非常重要的。

        如何寻找到中心值呢?理想情况下中值是最优的中心值——但是需要通过排序来寻找——这就陷入了自证的怪圈中。不过我们并不苛求极致的优化——有时候的苛求优化的操作只会将问题复杂化。通过“随机”选取部分值,在样本中选择最优的中心值,相比于从第一个开始作中心值的做法,已经是有了极大的优化。

1.2 直接插入排序优化

        快速排序算法每次能够将序列分化为两部分,递归地进行求解。然而,调用函数本身需要占用空间,也耗费时间。当规模较小时,采用在该情况下更优的排序算法,是一种权衡——总是在各种条件下,用最好的方法——符合贪心的要求。

1.3 并行处理

       快速排序最优也是O(nlogn)的复杂度,其他算法例如归并排序也是O(nlogn)的复杂度,快速排序有一个非常重要的特征。每一轮分类,都将数据分为两个部分,这两个部分彼此排序互不影响,因此可以并行地实现。

二、实验结果

实验结果

数据规模为100的排序对比:

因为优化过后速度过快,时间记录为0,因此效率得到一个无穷大。(/0)

数据规模为10,0000的排序对比:(均显示前100个数)

        可以看到,数量级为100000已经非常庞大了,对于计算机而言,使用快速排序算法,速度还是很快的,而且优化效率几乎达到一倍。

数据规模为1,0000,0000的排序对比:(均显示前100个数)

         在面对亿级的数据时,优化过的算法效率提升非常可观。

三、优化源码

中心值选取优化 

// ****************************************************************************************
    // optimized point 1: 
    // compare the first, last and middle one, swap the middle value recond with the first location 
    int middle=(i+j)/2;
    int index=(A[low]>=A[high] && A[low]<=A[middle])?low:
                     ((A[high]>=A[low] && A[high]<=A[middle])?high:middle);
    swap(A[index],A[low]);
//*****************************************************************************************

采取抽样的方法,前中后选取中间值。离散选取减小了因部分连续片段同时较大或较小而所选中间值偏大或偏小的可能性。

直接插入排序优化

 //****************************************************************************************
    // optimized opint 2:
    // if the size of array to be sorted is small enough, 
    // direct-insert-sort is much more efficient than quickSort 
    if(high-low<maxLen)     
    {
        for(i=low+1; i<=high; ++i)
        {
            temp=A[i];
            j=i-1;
            while(A[j]>temp&&j>=low)
            {
                A[j+1]=A[j];
                j--;
            }
            A[j+1]=temp;
        }
        return;
    }
//*****************************************************************************************

当数据规模足够小时(<maxLen=10(默认可调)),采用直接插入排序算法,避免了深层的递归,在权衡之下是更优的算法。 

并行优化排序

(这部分花了两天,并行在vscode要配置的东西和注意的事项太多了,一不小心就出错;此外,一股脑儿在递归函数里面用多线程,效率嘎嘎降,100000000个数据,不加判断就一个劲儿多线程,可以在80s左右完成。。。是不优化的6/7倍)

// ****************************************************************************************
// optimized point 3:
// via openmp, make it runs parallelly
//
// Actually, there are 2 options with different parallelly running structure as follows
//***********************option 1*********************************/
    // if(depth<=7)
    // {
    //     #pragma omp parallel  // starts a new team
    //     {
    //         #pragma omp sections
    //         {
    //             //{ Work1(); }
    //             #pragma omp section
    //             { QuickSort_optimized(A, i+1, high, depth+1); }
    //             #pragma omp section
    //             { QuickSort_optimized(A, low, j-1, depth+1); }
    //         }
    //     }
    // }
    // else
    // {
    //     QuickSort_optimized(A, i+1, high, depth+1);
    //     QuickSort_optimized(A, low, j-1, depth+1);
    // }
//***********************option 2*********************************/
    if(depth<=7)
    {
        #pragma omp parallel num_threads(2)
        {
            #pragma omp single
            {
                #pragma omp task
                QuickSort_optimized(A, i+1, high, depth+1);
                #pragma omp task
                QuickSort_optimized(A, low, j-1, depth+1);
                #pragma omp taskwait
            }
        }
    }
    else
    {
        QuickSort_optimized(A, i+1, high, depth+1);
        QuickSort_optimized(A, low, j-1, depth+1);
    }
}
//**************************************************************************************

值得说明的是,如果直接无脑用并行子部分排序,由于快速排序是的递归函数,每一次并行都会产生两个线程。首先就是线程随着递归深度以指数爆炸的方式产生。其次,线程之间处理也随着线程的增多而发生冲突,产生意想不到的拖延“死锁”。再次,众多线程的创建、运维、消除的声明周期,同样占用着大量的时间与资源。因此,这里采用浅层并行,深层串行的方式,着重凸显线程并发的优势,规避其劣势。


总结

除了上述三种优化方法,还有如尾预编译等优化手段,可以查阅其他大牛的博客。

需要基础快排/优化快排源代码的,可以评论区喊我。

一起学习,一起加油!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
数据结构与算法 排序算法 内排序 八大基础排序 选择排序 简单选择排序 思想 每次选择最大的数插入到末尾中 做法 外层for循环控制次数 内层for循环找出最大的值的角标 找出最大角标后,进行交换 优化思路 同时获取最大值和最小值,然后分别插入数组的首部和尾部 堆排序 思想 使用大顶堆的思想来排序,每次建堆后交换 做法 总体:建堆-替换 建堆 只要左子树或右子树大于当前根节点,则替换 替换后会导致下面的子树发生了变化,因此同样需要进行比较,直至各个节点实现父>子这么一个条件(递归) 交换排序 冒泡排序 思想 每次俩俩交换,将最大值交换到末尾 做法 外层for控制循环次数 内层for控制比较次数 每次循环之后,比较次数都会减少一次 优化思路 如果一趟排序后没有发生位置变化,那么此时就是有序了 快速排序 思想 每次将比支点小的放在支点左边,比支点大的放在支点右边 做法 外循环while只要i和j不碰撞查找 内层两个while循环分别查找出比支点小的和比支点大的角标 如果i<=j满足条件,就交换 一趟排序后,支点的左边都比支点小,支点右边都比支点大 只要满足L<j,i0的条件查找出要插入的何时位置 退出内层while循环后就找到了合适的位置插入 优化思路 二分查找插入,找合适位置的时候使用二分查找算法 希尔排序 思想 用增量来将数组进行分隔,直到增量为1。底层干的还是插入排序干的活 做法 最外层for外循环控制增量的数量,每次/2 第二层for循环控制每次增量那组开始进行插入排序,直至完毕 第三层while循环找到要插入到哪个位置 归并排序 思想 将两个已排好序的数组合并成一个有序的数组 做法 递归拆分出两个有序的数组,从mid的位置开始拆分,递归出口:只有一个值的时候就不用拆分了 合并两个有序的数据 分别往两个数组填充已有序的数据 比较两个数组的值谁小,谁小就放到我们的数组中 如果比较完之后还有剩余的数据,那么用while直接添加到我们的总数组中 优化思路 当递归到规模足够小时,利用插入排序 归并前判断一下是否还有必要归并 只在排序前开辟一次空间 基数(桶)排序 思想 分配,回收(分配到不同的位置上,然后回收)..不断分配..回收来进行排序,直到有序 做法 分配一个[array.length][10列]的二维数组来装我们的元素 最外层for循环控制要分配和回收的次数(根据最大值) 将元素的个、十、百位依次放到桶子上(第一次就是放个位,第二次放十位) 依据每列回收桶子,两个for循环 外排序 查找算法 二分查找 分块查找 哈希查找 贪心算法 求最小生成树的Prim算法和Kruskal算法 爬山问题 回溯算法 n皇后问题 动态规划Dynamic Planning 应用 求最长公共子序列LCS 矩阵连乘问题 爬楼梯问题 找零问题 0-1背包问题 分治算法Divide and Conquer 应用:归并排序 其它 Rabin fingerprints 文件指纹算法 BitMap 位图算法 BloomFilter 布隆过
道01数据结构和算法绪论. mp402_谈谈算法. mp4 西03_时间复杂度和空间复杂度.mp404_时间复杂度和空间复杂度2.mp405_时间复杂度和空间复杂度3.mp4险06线性表. mp407_线性表2. mp408_线性表3. mp4品09_ 线性表4. mp410_线性表5. mp411_线性表6. mp4@12_线性表7. mp413_线性表8. mp4西14. 线性表9. mp415_线性表10. mp4 16_单链表小结:腾讯面试题. mp4品17_ 线性表12. mp418_约瑟夫问题. mp4西19_ 线性表14. mp4 20_魔术师发牌问题. mp421线性表16. mp4逾22_ 线性表17. mp423_栈和队列. mp424_栈和队列2. mp4面25_ 进制转换. mp4面26_ 栈和队列4. mp427_逆波兰计算器mp4 28_中缀表达式转换为后缀表达式01. mp4逾29_ 中缀表达式转换为后缀表达式02. mp430_栈和队列7. mp431_栈和队列8. mp4 西32递归和分治思想.mp433_递归和分治思想2. mp434_汉诺塔. mp4 35_八皇后问题. mp4 四36_字符串.mp4 二37_ KMP算法. mp4 四71斐波那契查找(黄金分割法查找).38_ KMP算法2. mp4 立39_ KMP算法之NEXT数组代码原理分析. mp4二40_ KMP算法之实现及优化. mp4二41树. mp4 四42_树的存储结构. mp443_树的存储结构2. mp4四44_二艾树. mp445_二叉树2. mp4 46_二又树的存数结构. mp447_二又树的遍历. mp4 48_二丈树的建立和遍历算法. mp4四49_线索二叉树. mp4 50_线索二又树代码实现. mp4 画51_树、森林及二又树的相互转换. mp452_赫夫曼树. mp453_赫夫曼编码. mp4 四54_赫夫曼编码C语言实现. mp4口55_图. mp4 逾56_图的定义与术语2. mp457_图的存储结构. mp4 58_图的存储结构(邻接表) . mp4 59_图的存储结构(十字链表、邻接多重表、边集数组) . mp4四93堆排序的代码实现mp460_图的遍历(深度优先遍历) . mp4 品94归并排序. mp4 四61_马踏棋盘算法(骑士周游问题) . mp4 95归并排序(迭代实现) . mp4品62_图的遍历(广度优先遍历) . mp4 國96快速排序.mp4 63_最小生成树(普里姆算法) . mp4 二97快速排序优化mp464_最小生成树( 克鲁斯卡尔算法) . mp4 立98总结回顾.mp4画65_最短路径(迪杰斯特拉算法).mp466_最短路径( 弗洛伊德算法) . mp4口67拓扑排序. mp4二68关键路径.mp4口69_查找算法. mp4 画69关键路径(代码讲解).mp4
好的,我可以为您提供一些关于数据结构、内部算法与排序课程设计的建议。 首先,您需要确定该课程设计的目标和学习内容。您可以考虑以下几个方面: 1. 数据结构的基本原理和常用数据结构,如线性表、栈、队列、树、图等。 2. 内部算法的基本概念和分类,如贪心算法、分治算法、动态规划算法等。 3. 排序算法的基本原理和分类,如冒泡排序、选择排序、插入排序、快速排序、归并排序等。 4. 采用的编程语言和开发工具,如C++或Java编程语言,Visual Studio或Eclipse开发工具等。 5. 可能遇到的问题和解决方案,如算法复杂度分析、算法实现错误、排序算法优化等。 接下来,您可以根据以上考虑的内容,制定课程设计的详细计划。以下是一个可能的课程设计计划: 1. 第一周:介绍数据结构的基本原理和常用数据结构,讲解线性表、栈、队列、树、图等数据结构的实现和应用。 2. 第二周:学习内部算法的基本概念和分类,讲解贪心算法、分治算法、动态规划算法等算法的实现和应用。 3. 第三周:介绍排序算法的基本原理和分类,讲解冒泡排序、选择排序、插入排序、快速排序、归并排序等排序算法的实现和应用。 4. 第四周:实现一个基于数据结构和内部算法的实际应用,如一个简单的迷宫求解器或文本编辑器。 5. 第五周:总结和回顾整个课程设计的过程,讨论可能的改进和优化方案。 希望这些建议能够对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值