原创文章,转载请注明出处
1. 排序算法简介
提起排序算法,相信大家并不陌生。最常见也是最基础的有:插入排序,选择排序,冒泡排序。这三种排序的平均复杂度都是,实现起来简单,在小规模排序中有大量的应用。其中插入排序由于其是稳定的、原地的排序而受到广大群众的欢迎。在其基础上衍生出来的高级版本—“希尔排序”则是效率更高的插入排序。
但是如果数据量非常大,的时间复杂度我们似乎也不太能接受,这样我们又引申出了一些时间复杂度为的排序算法,其中最常见的就是我们今天的两个主角:归并排序与快速排序。
再往上走,似乎的算法也不太满足我们了。别担心,我们还有的算法—桶排序、计数排序等。这一类算法做到了线性的时间复杂度,效率非常高,但是其对数据的要求也相应提高,并不是所有情况都可以使用。
2. 为什么要使用这种排序算法?
现在我们对常见的排序算法有了一定的认识,那我们应该如何来评判一个排序算法的好坏呢?在特定的情况下,我们又该如何选择合适的算法呢?
通常来看,我认为选择排序算法时有几点需要考虑:
- 时间复杂度。大多数情况下,这是一个决定性的因素,至少可以帮助我们筛选掉大部分算法。
- 空间复杂度。虽然现在的内存不值钱了,而且得益于大部分语言都有的垃圾清除机制,空间复杂度可能不是我们首先需要考虑的条件。但是当算法的空间使用量超出我们所希望的阈值,甚至频繁触发了GC,那我们就需要进行仔细的评估了。PS: 对于空间复杂度为的排序算法我们称之为原地排序。
- 算法的稳定性。这里的稳定性指的是元素之间的顺序的稳定性,举个例子:
1,8,3,2,8,6
对于这样一串数字,我们按照从小到大进行排序:
1,2,3,6,8,8
在原数组中,存在两个相同的元素8
,一个在前一个在后,那么对于排序以后的数组,如果这两个8的先后位置没有改变,那么这次排序是稳定的。
可能有朋友会觉得,这两个8位置变不变有什么关系,反正都一样。我们可以思考一下,如果我们排序的不是数字,而是一个对象。假如我们按照对象的A属性对其排序,但是又希望在A属性相同的条件下,不改变容器中对象之间原有的顺序。这时算法的稳定性就是一个重要的指标了。
实际场景中,在对名字、手机号码等进行排序时,我们往往会需要一个稳定的算法。 - 数据规模。私以为这一点的重要性不亚于时间复杂度,而且往往有一票否决权。如果我们在数据量非常小的时候还是不顾一切选择快排,那么快排中选择pivot的操作所占的比重将会超出我们的预期,导致其效率可能还不如O(n^2)的算法。
同样举个例子,我们在Java开发中,对于数据的排序,只需要来一行Arrays.sort()
就可以了,但是我们有没有思考过,为什么无论数据量大小,Arrays.sort()
都能很快的运行?它用的究竟是什么鬼才算法?
源码这里就不贴了,有兴趣的朋友可以自行查看。这里我只将其中的主要逻辑进行列举(JDK 1.8):
- 数据量小于47,使用插入排序。
- 数据量小于286,使用快排。
- 根据数据情况再选择使用快排还是归并排序。
可以看到强如Arrays.sort()
这样的工业级排序函数,也并非是靠一个算法吃到底。
在什么情况下用什么工具?为什么要用?是我们在编程过程中要不断问自己的两个问题。