初级排序
选择排序
选择排序:找到数组中最小的和第一位交换位置,再从剩下的元素中找到最小的和第二个元素互换位置,一支往复到整个数组排序完成。类似于拿到一手杂乱的牌整牌的过程。
1 | public static<T extends Comparable<T>> void SelectionSort(T[] list){ |
复杂度
大约要比较N^2/2次, 进行N次交换,复杂度O(n^2)
插入排序
插入排序是将元素插入一个已经排序的序列的适当位置,相当于一边起牌一边整理手中已经排序了的牌.举个栗子,对5,3,8,6,4这个无序序列进行简单插入排序,首先假设第一个数的位置时正确的,想一下在拿到第一张牌的时候,没必要整理。然后3要插到5前面,把5后移一位,变成3,5,8,6,4.想一下整理牌的时候应该也是这样吧。然后8不用动,6插在8前面,8后移一位,4插在5前面,从5开始都向后移一位。注意在插入一个数的时候要保证这个数前面的数已经有序。
1 | public static<T extends Comparable<T>> void InsertionSort(T[] list){ |
复杂度
这个排序方式复杂度取决于数据的有序性,如果已经是大致有序数组就会效率比较高。
但依旧是O(n^2),对于随机数据选择排序和插入排序表现差不多。
希尔排序是对插入排序的改进,移步:https://segmentfault.com/a/1190000013967025
希尔排序的性能甚至不差于高级排序,且代码量少,且不需要额外的内存空间。算法第四版给出了一个性能不错递增序列:{1,4,13,40,121,364…}
希尔排序:
1 | public static<T extends Comparable<T>> void ShellSort(T[] list){ |
冒泡排序
1 |
|
归并排序
将元素分为两半分别排序再将结果归并起来,归并排序能将任意长度为N的数组的排序时间和NlogN成正比,但是他需要额外的N正比的存储空间。
O(logN)
自顶向下的归并排序
自顶向下就是从整个的数组,变成小数组,分成小问题来解决,也就是递归。
我们首先定义将两段有序数组归并的函数merge。
归并排序是一个分治思想,用递归实现。一个数组被分成左右两边进行排序,左右两边有能继续划分左右,直至左右两边都只有一个元素。分成两组后,将链遍分别排序之后用merge函数合并(实际上排序被划分为两个数字比较的子问题)。
1 | static Comparable[] copy; |
自底向上的归并排序
相对于自顶向下的分治,自底向下是从小问题到大问题。我们先22归并,在44归并,88归并。。直到最后,可能最后一次归并后半段数组长度比前半段小
1 | public static<T extends Comparable<T>> void MergeSort2(T[] list){ |
快速排序
快速排序是原地排序(只需要一个很小的辅助栈),且内循环短小,所以无论是理论上还是实际中他都更快一些。复杂度O(logN)。
快速排序也是分治的思想,用递归来实现。每一轮排序我们将一个元素位置排定,即它的左边元素都比它小,右边的元素都比它大,数学归纳法可知N次排序能将所有的元素排好顺序。每一轮排序我们选定一个要排定的元素,通常选择list[lo],j是最右边的元素,j向左走,当遇到比list[lo]小的元素停止,i从最右边出发,遇到比list[lo]大的数字停止,i和j互换元素。然后继续,还是j先出发,遇到比list[lo]小的数字停止,i开始出发。。。这样继续直到i和j相遇,将list[lo]和相遇位置的元素互换位置,这一轮结束,我们称这个过程为切分partition。然后利用递归将该元素左边的数组和右边的数组也进行如此切分,直到全部有序。
快速排序最好的性能是每次能将数组对半分,为了避免数组不够随机带来的效率低下,在进行快速排序之前我们先将数组打乱。
切分
1 |
|
排序
为了保证效率,我们在进行排序之前应该先
1 | public static<T extends Comparable<T>> void QuickSort(T[] list){ |
三项切分的快速排序
针对有很多重复元素的快速排序
1 | public static<T extends Comparable<T>> void QuickSort3Way(T[] list, int lo, int hi){ |
其他提高快排性能的点:
- 每次选择中轴元素的时,候取中位数,但是牺牲了计算中位数的时间,所以可以每次随机取3个数字计算中位数。
- 当排序的规模小于一定值是使用插入排序。因为小规模的排序插入排序性能优于快速排序,因为快排使用递归,递归速度很慢,在小规模数据时(例如N<50),可能还不如插入排序快。 (递归效率低是函数调用的开销导致的。在一个函数调用之前需要做许多工作,比如准备函数内局部变量使用的空间、搞定函数的参数等等,这些事情每次调用函数都需要做,因此会产生额外开销导致递归效率偏低,所以逻辑上开销一致时递归的额外开销会多一些)