归并与快排——1.如何选择合适的排序算法

原创文章,转载请注明出处

1. 排序算法简介

提起排序算法,相信大家并不陌生。最常见也是最基础的有:插入排序,选择排序,冒泡排序。这三种排序的平均复杂度都是《归并与快排——1.如何选择合适的排序算法》,实现起来简单,在小规模排序中有大量的应用。其中插入排序由于其是稳定的原地的排序而受到广大群众的欢迎。在其基础上衍生出来的高级版本—“希尔排序”则是效率更高的插入排序。
但是如果数据量非常大,《归并与快排——1.如何选择合适的排序算法》的时间复杂度我们似乎也不太能接受,这样我们又引申出了一些时间复杂度为《归并与快排——1.如何选择合适的排序算法》的排序算法,其中最常见的就是我们今天的两个主角:归并排序与快速排序。
再往上走,似乎《归并与快排——1.如何选择合适的排序算法》的算法也不太满足我们了。别担心,我们还有《归并与快排——1.如何选择合适的排序算法》的算法—桶排序、计数排序等。这一类算法做到了线性的时间复杂度,效率非常高,但是其对数据的要求也相应提高,并不是所有情况都可以使用。

2. 为什么要使用这种排序算法?

现在我们对常见的排序算法有了一定的认识,那我们应该如何来评判一个排序算法的好坏呢?在特定的情况下,我们又该如何选择合适的算法呢?
通常来看,我认为选择排序算法时有几点需要考虑:

  1. 时间复杂度。大多数情况下,这是一个决定性的因素,至少可以帮助我们筛选掉大部分算法。
  2. 空间复杂度。虽然现在的内存不值钱了,而且得益于大部分语言都有的垃圾清除机制,空间复杂度可能不是我们首先需要考虑的条件。但是当算法的空间使用量超出我们所希望的阈值,甚至频繁触发了GC,那我们就需要进行仔细的评估了。PS: 对于空间复杂度为《归并与快排——1.如何选择合适的排序算法》的排序算法我们称之为原地排序
  3. 算法的稳定性。这里的稳定性指的是元素之间的顺序的稳定性,举个例子:
    1,8,3,2,8,6
    对于这样一串数字,我们按照从小到大进行排序:
    1,2,3,6,8,8
    在原数组中,存在两个相同的元素8,一个在前一个在后,那么对于排序以后的数组,如果这两个8的先后位置没有改变,那么这次排序是稳定的。
    可能有朋友会觉得,这两个8位置变不变有什么关系,反正都一样。我们可以思考一下,如果我们排序的不是数字,而是一个对象。假如我们按照对象的A属性对其排序,但是又希望在A属性相同的条件下,不改变容器中对象之间原有的顺序。这时算法的稳定性就是一个重要的指标了。
    实际场景中,在对名字、手机号码等进行排序时,我们往往会需要一个稳定的算法。
  4. 数据规模。私以为这一点的重要性不亚于时间复杂度,而且往往有一票否决权。如果我们在数据量非常小的时候还是不顾一切选择快排,那么快排中选择pivot的操作所占的比重将会超出我们的预期,导致其效率可能还不如O(n^2)的算法。
    同样举个例子,我们在Java开发中,对于数据的排序,只需要来一行Arrays.sort()就可以了,但是我们有没有思考过,为什么无论数据量大小,Arrays.sort()都能很快的运行?它用的究竟是什么鬼才算法?
    源码这里就不贴了,有兴趣的朋友可以自行查看。这里我只将其中的主要逻辑进行列举(JDK 1.8):
  • 数据量小于47,使用插入排序。
  • 数据量小于286,使用快排。
  • 根据数据情况再选择使用快排还是归并排序。

可以看到强如Arrays.sort()这样的工业级排序函数,也并非是靠一个算法吃到底。

在什么情况下用什么工具?为什么要用?是我们在编程过程中要不断问自己的两个问题。

    原文作者:鬼畜的猪
    原文地址: https://www.jianshu.com/p/4422bced07e4#comments
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞