数据结构与算法之排序优化篇

一、如何选择合适的排序算法?

如果要实现一个通用的、高效率的排序算法,我们应该选择哪种排序算法?下面是几种常见排序算法。

是 稳 謐 誑 序 ? X 0 〔 冫 0 冫 划

前面的学习过,线性排序算法的时间复杂度比较低,适用的场景比较特殊。所以如果要写一个通用的排序函数,不能选择线性排序算法。

如果对小规模数据进行排序,可以选择时间复杂度是O(n^2)的算法;如果对大规模的数据进行排序,时间复杂度是O(nlogn)的算法更加高效。所以,为了兼顾任意规模数据的排序,一般都会首选时间复杂度是O(nlogn)的排序算法来实现排序。

对于时间复杂度是O(nlogn)的排序算法不止一个,我们已经讲过的有归并排序,快速排序,还有推排序。堆排序和快速排序都有比较多的应用,比如Java语言采用堆排序实现排序函数,C语言使用快速排序实现排序函数。

前面学习的过,快排在最坏的情况下的时间复杂度是O(n^2),而归并排序可以做到平均情况、最坏情况的时间下的时间复杂度都是O(nlogn),从这点上看,归并排序更加有优势,那为什么使用的少呢?

前面学习过,归并排序并不是原地排序算法,空间复杂度是O(n)。所以,夸张地讲,如果要排序100MB的数据,除了数据本身占用的内存之外,排序算法还要额外在占用100MB的内存空间,空间耗费就翻倍了。

前面学习过,快速排序比较适合来实现排序函数,但是,我们也知道,快速排序在最坏情况下的时间复杂度是O(n^2),如何来解决这个"复杂度恶化"的问题呢?

二、如何让优化快速排序?

(1)、为什么最坏情况下快速排序的时间复杂度是O(n^2)呢?

如果数据原来就是有序的或者接近有序的,每次分区点都选择最后一个数据,那快速排序算法就会变得非常糟糕,时间复杂度就会退化为O(n^2)。实际上,这种O(n^2)时间复杂度出现的主要原因还是因为我们分区点不够合理。

(2)、如何让选择分区点?

最理想的分区点:被分区点分开的两个分区中,数据的数量差不多。

如果粗暴地直接选择第一个或者最后一个数据作为分区点,不考虑数据的特点,肯定会出现在某些情况下,排序的最坏情况时间复杂度是O(n^2)。为了提高排序算法的性能,我们也要尽可能地让每次分区都比较平均。

下面是两种比较常用、比较简单的分区算法;

1、三数取中法

我们从区间中的首、尾、中间,分别取出一个数,然后对比大小,取这3个数的中间值作为分区点。这样每间隔某一固定的长度,去除数据比较,将中间值作为分区点的分区算法,肯定要比单纯取某一个数据更好。但是,如果要排序的数组比较大,那"三数取中"可能就不够了,可能要"五数取中"或者"十数取中"。

2、随机法

随机法就是每次从要排序的区间中,随机选择一个元素作为分区点。这种方法并不能保证每次分区点都选的比较好,但是从概率的角度来看,也不大可能会出现每次分区点都选的很差的情况,所以平均情况下,这样选的分区点是比较好的。时间复杂度退化为最糟糕的O(n^2)的情况,出现的可能性不大。

通常,快速排序使用递归来实现的。我们在数据结构与算法之递归篇学习过,递归要警惕堆栈溢出。为了避免快速排序里,递归过深而堆栈过小,导致堆栈溢出,通常有两种方法:第一种是限制递归深度。一旦递归过深,超过了我们事先设定的阈值,就停止递归。第二种是通过在堆上模拟实现一个函数调用栈,手动模拟递归压栈、出栈的过程,这样就没有系统栈大小的限制。

举例分析排序函数

以C语言中的qsort()函数为例。虽然qsort()从名字上看,很像是基于快速算法实现的,实际上它并不仅仅用了快排这一种算法。

在源码中,qsort()会优先使用归并排序来排序输入数据,因为归并排序的空间复杂度O(n),所以对于小数据量的排序,比如1KB、2KB等,归并排序额外需要1KB、2KB的内存空间,这个问题不大。现在计算机的内存都挺大的,我们很多时候只求的是速度。

但是如果数据量太大,排序100MB的数据,这个时候再用归并排序就不合适了。所以要排序数据量比较大,qsort()会改用快速排序算法来排序。

那qsort()是如何选择快速排序算法的分区点呢?在源码中,qsort()选择分区的方法就是"三数取中"法。

对于前面学的递归太深会导致堆栈溢出的问题,qsort()是通过自己实现一个堆上的栈,手动模拟递归来解决。

实际上,qsort()并不仅仅是用到归并排序和快速排序,他还用到插入排序。在快速排序的过程中,但要排序的区间中,元素的个数小于等于4时,qsort()就退化为插入排序,不再继续用递归来做快速排序,因为在小规模数据面前,O(n^2)时间复杂度的算法并不一定O(nlogn)的算法执行时间长,举例分析:

假设K=1000,c=200,当我们对小规模数据( 比如=100)排序时,n^2的值实际上比knlogn+c还要小。

k n logn•c leae * lee * leaee loglae * 2ee leeøe nA2 lea*løe

所以,对于小规模数据的排序,O(n^2)的排序算法并不一定比O(nlogn)排序算法执行的时间长。对于小数据量的排序,我们选择比较简单,不需要递归的插入排序算法。

其次,在qsort()插入排序的算法实现中,也利用了这种编程技巧。


扫码关注微信公众号,欢迎技术交流,其中含有大量免费的人工智能、图像处理、IT资料:

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值