什么是稳定性:
同样值的个体间,如果不因为排序而改变相对次序,则这个排序就有稳定性,否则就没有
稳定性的意义:
对于基本类型而言,排序算法的稳定性作用不大。但是对于非基础类型,是存在一定的价值的。比如对于学生信息先按age进行由小到大的排序,排好后再按班级进行排序。此时如果排序算法是稳定的,则在按班级分好班后,相同年龄的学生不会相对顺序不会改变。
不具备稳定性的排序:
选择排序、快速排序、堆排序
具备稳定性的排序:
冒泡排序、插入排序、归并排序、一切桶排序思想下的排序
为什么具备/不具备稳定性?
时间复杂度O(N^2)的三个排序: 1.选择排序: 假设原数组为[3*,3,3,1,3,3],选择排序每次将最小/大值放置在数组末/首。第一次排序后数组为[1,3,3,3*,3,3],3相较于其他3位置发生了变化 2.冒泡排序:假设原数组为[6,4,5,6]。在第一轮排序中,6和4、5交换完后到6前面,就不会再进行交换了(相等没必要交换),第一轮排完序后数组为[4,5,6,6]。后几轮排序类似 3.插入排序:假设数组为[3,2*,2,5]。首先0~0数组上有序;然后看0~1数组,需要交换得到数组[2*,3,2,5];然后看0~2数组,需要交换得到数组[2*,2,3,5](相等没必要交换),2和2*的相对位置没有改变
时间复杂度O(NlogN)的三个排序: 1.归并排序:假设原数组为[1*,1**,2,1,2,3]左半部分为[1*,1**,2],右半部分为[1,2,3],此时把这两个部分进行merge操作,遇到左右指针指向的值相等时,全部将左指针指向的值放入help数组中即可,merge后数组为[1*,1**,1,2,2,3] 2.快速排序:假设原数组为[6*,7,6,6,3,5]。此时以5为划分值,因为3<5,所以将3放置在小于区域,就要将3和6进行交换 [3,7,6,6,6],6和其他6的相对次序发生了变化。 3. 堆排序:假设原数组为[5,4,4,6],排好序后数组为[6,5,4,4*],具体过程参考堆排序过程进行模拟。 非比较排序: 桶排序:遵循先入桶的元素先出桶,所以是具有稳定性的。
tips:
1.基于比较的排序没有时间复杂度在O(NlogN)以下的排序 2.没有时间复杂度为O(NlogN),空间复杂度O(1),且稳定的排序 3.不存在将数组中的奇数放左边,偶数放右边,且相对次序不变的方法。 解决这个问题的思想就是运用快速排序中的将小于等于划分值的放左边,大于划分值的放右边,这是不稳定的。 4.工程上对排序的改进有两个方面: ①充分利用O(NlogN)和O(N^2)排序各自的优势:比如在下面快排代码中,多做了一个判断;当需要排序的数组较小时, 可以使用插入排序使得代码跑的更快。即使插入排序的时间复杂度为0(N^2),但是当N较小时是要优于快排的O(N*logN)的:
public static void quickSort(int[] arr, int L, int R) {
if (L == R){
return;
}
if (L > R-60){
在arr[L...R]进行插入排序
return;
}
swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
int[] p = partition(arr, L, R);
quickSort(arr, L, p[0] - 1);
quickSort(arr, p[1] + 1, R);
}
②稳定性的考虑: 在系统自带的排序函数中,当输入值为基本类型时,会使用不具备稳定性但是时间复杂度较低的快速排序;当输入值为非基本类型时就会采用具备稳定性且时间复杂度较低的的归并排序
|