一.排序算法的稳定性及其汇总
排序算法的稳定性是一个重要的特性,它描述了排序算法在排序过程中是否会改变具有相等键值的元素之间的相对顺序。如果一个排序算法能够保持任何两个相等的元素的先后顺序不变,那么这个排序算法就是稳定的;反之,如果排序过程中可能改变相等元素之间的先后顺序,则该算法是不稳定的。
稳定的排序算法对于某些应用场景是非常重要的,特别是当元素的键值不是唯一标识时。例如,在一个由学生组成的列表中,如果我们首先按照成绩排序,然后又想按照姓名排序,而且希望在姓名排序后,具有相同姓名的学生之间仍然保持按成绩排序的结果,这时就需要一个稳定的排序算法。
表格汇总:(常用的几个排序算法)
时间复杂度 | 空间复杂度 | 稳定性 | |
选择排序 | O(N²) | O(1) | 不稳定 |
冒泡排序 | O(N²) | O(1) | 稳定 |
插入排序 | O(N²) | O(1) | 稳定 |
归并排序 | O(Nlog N) | O(N) | 稳定 |
快速排序 | O(Nlog N) | O(log N) | 不稳定 |
堆排序 | O(Nlog N) | O(1) | 不稳定 |
桶排序 | O(N+K) | O(N+K) | 稳定 |
注意:目前没有找到时间复杂度O(N*IogN),额外空间复杂度O(1)(原地排序),又稳定的排序。
在选择排序算法中,默认快排最高效。
二.如何选择排序算法
1.最常用的排序算法
快速排序:由于其平均时间复杂度为O(nlogn),并且常数因子相对较小,快速排序在实践中非常流行和高效,尤其是在数组排序中。它不是稳定的排序算法,但其优秀的平均性能和原地排序的特性使其成为许多情况下的首选。
归并排序:归并排序提供稳定的排序并且时间复杂度稳定为O(nlogn),特别适合于需要稳定排序或处理链表等非随机访问外部存储的场景。它需要O(n)的额外空间,这是它的主要缺点。
堆排序:堆排序也提供O(nlogn)的时间复杂度,并且是原地排序,但它不是稳定的。堆排序在某些特定场景下(如需要排序的数据集非常大且不能全部加载到内存时)非常有用。
2.效率最高的排序算法
对于大规模数据集:快速排序通常因其O(nlogn)的平均时间复杂度和较低的内存使用而被认为是最高效的。它的变种如三向切分快速排序对于有大量重复元素的数据集更加高效。
对于小到中等规模的数据:或者数据已经部分排序的情况,插入排序和希尔排序可以非常高效。尽管它们的最坏情况时间复杂度较高,但在特定情况下它们的实际性能可能优于更复杂的排序算法。
特定情况下:如当内存是限制因素时,外部排序(如归并排序的外部变体)可能是最有效的选择。
3.实际应用
在许多现代编程语言和库中,标准排序函数通常是对几种排序算法的优化组合。例如:
C++中:
std::sort
是最常用的排序函数,定义在<algorithm>
头文件中。std::sort
通常实现为快速排序的一个变体,但标准并未明确指定使用的具体算法,只要求其平均时间复杂度为)O(nlogn)。std::sort
是一个非稳定排序,可以用于任何满足随机访问迭代器的容器,如std::vector
和std::deque
。Python中:
sorted()函数
底层实现是Timsort算法,一种高效、稳定的排序算法,结合了归并排序和插入排序的优点,尤其优化了在实际数据中常见的各种场景。函数在底层实现中使用了归并排序和,这些算法旨在提供稳定的高性能排序,适应各种不同的数据分布。Timsort算法在最坏情况下的时间复杂度为O(nlogn),在最好情况下(即输入已经部分排序)的时间复杂度可以接近O(n)。最终,选择哪种排序算法应基于数据的具体特性(如数据大小、是否存在大量重复值、是否需要稳定排序等),以及应用场景的特定需求(如对时间或空间效率的要求)。
实例问题:为什么基础类型用快速排序,非基础(自己定义)类型的数据用归并排序?
解答:基础类型数据稳定性不用考虑,所以使用常数时间较低的快速排序。如果是非基础类型的数据,不知道需不需要考虑稳定性,所以用归并排序保持其稳定性。