常见的排序算法
我们常见的排序算法有 选择排序、冒泡排序、插入排序、归并排序、随机排序、堆排序、计数排序和基数排序。
其中基数排序和计数排序不是基于比较的排序
那么基于比较的排序和不基于比较的排序有什么区别呢?
1.基于比较的排序,它的适用性比较高,使用的范围比较广,我们只需要提供一个比较器,所有的基于比较的排序,都能够通过它进行排序。而不基于比较的排序,它的使用范围比较窄,通常,不基于比较的排序往往对数据的限制比较高,假如说我们使用基数排序,基数排序在一般情况下,排序的数据都是正数,当然我们可以对经典的进行改造,让其可以支持负数,比如我们找出最低的负数,将其加为0,当排完序后,我们再减回来,虽然可以支持到负数,但我们仍需考虑溢出的问题。
public static void test(){
int[] arr = {-5,-8,6,7,5,-3,6};
//找出最小值为 -8
//然后把所有的值都 加 8
// arr = {3,0,14,15,13,5,14};
//然后进行基数排序
//排完序后再减 8
}
对于非基本数据类型,基数排序对数据要求严格,不易改写。
不基于比较的排序有这么多限制,那他有什么优势吗?
答案是肯定的,不基于比较的排序,它的时间复杂度可以优化至O(n)
对于基于比较的排序,它们的时间复杂度极限是O(nlogn),希尔排序可以优化值O(nlog1.3),但O(nlog1.3)的复杂度没有O(nlogn)好。
在此奉上常见排序的关系对比
其中,归并排序和堆排序是严格的O(nlogn),而随机快排的时间复杂度是在概率上求的一个长期期望。
归并 随机快排 堆排序的选择
归并 随机快排 堆排序 它们的时间复杂度都是O(nlogn),我们在使用时该如何选择呢,通过上图我们可以发现,
1.如果我们希望排序效率高又稳定,那么我们可以选择归并排序,因为随机快排和堆排序不稳定。
2.如果我们希望用的空间少,那么我们可以选择堆排序,它的空间复杂度是O(1),我们可以使用有限的几个变量来完成。
3.如果我们就希望排序快,那么我们可以选择快速排序,因为他的常数比较低,经过大量的实验证明,快速排序的排序时间比归并和堆用的少。
总结 :为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并
时间复杂度O(N*logN)、额外空间复杂度低于O(N)、且稳定的基于比较的排序是不存在的。
排序常见的坑
1.归并排序的额外空间复杂度可以变成O(1)。
有一种说法说通过内部缓存法可以将归并排序的空间复杂度降至O(1),我觉的这是没有任何意义的,首先,通过内部缓存法的确可以将归并排序的空间复杂度降至O(1),但通过该方法,归并排序将变的不再稳定,如果归并排序不再稳定,那么我们选择归并排序的意义又是什么呢?如果我们图空间复杂是O(1),那我们用堆排序它不香吗。
2.原地归并排序
还有一种原地归并排序的说法,它说通过原地归并排序,归并排序的空间复杂度即可以变成O(1),而且还稳定。但是,我们使用原地归并排序这个技巧后,我们发现时间复杂度竟然会变成 O(n²),这样的话,我们用插入排序岂不是更好。
3.快速排序稳定性改进
“0-1 stable sort” 这么一篇论文说快速排序可以变成稳定的,里面提到对数据样本有很多的要求, 快速排序本来是对数据样本没有限制的,如果对数据样本有限制,直接使用基数排序岂不快哉。
工程上对排序算法的改进
在一般情况下,如果我们的数据量在60左右或之下,我们可以使用插入排序,因为插入排序虽然时间复杂度为O(n²),但其常数项优秀,通过大量实验证明,在数据量较小的时候,插入排序凭借其较小的常数项,它的排序速度在快速排序之上,而快速排序在数据量大的时候,它优秀的调度会使它的排序速度非常快。