排序算法
一、稳定性
何为稳定:假设A==B 排序前A再B前面,排序后A依然在B前面,这就是稳定的排序算法
如冒泡排序 插入排序 归并排序可以是稳定的。快速排序 选择排序是不稳定的。
稳定性跟具体算法实现有关:我们说冒泡排序是稳定的 如果我们的冒泡算法判断A>=B 则交换A和B,这种写法那就不稳定了
因为A==B的情况下 最终排序后A在B之后了。
稳定性好处:多重排序某些场景可以避免 不必要的二次排序,我们就拿经常举的例子来看
加入全年级学生已经按学号进行了排序,再按班级排序后,需要满足同一班级的学生学号也是排序的
这样稳定的排序算法 按班级排序后,就已经满足要求了;非稳定的排序那必须再进行学号的排序。
二、10大基本排序算法
1、冒泡排序
相连元素两两比较,每轮把最大的往后移,
一轮之后最大的数在最后位置
二轮之后第二大的数在倒数第2的位置
...
2、选择排序
每次选择1个位置存在最大的数
一轮从第2个数开始每个跟第1个数比较,比其大就替换,第一个位置的数最大
二轮从第3个数开始每个跟第2个数比较,比其大就替换,第二个位置的数次大
...
3、快速排序
每次拿出一个数作为基数,一帮是开头的数,后面的数比其小就放入其左边,比其大就放入其右边,左右两个部分再这样处理
二分的思想。
代码如下:
public class QuickSort { public static void quickSort(int[] array) { if (array == null || array.length <= 1) { return; } quickSort(array,0,array.length - 1); } /** * 快排 * @param array 数组 * @param start 起点 * @param end 终点 * @return 排序后的array */ public static void quickSort(int[] array, int start, int end) { if (array.length < 1 || start < 0 || end >= array.length || start > end) { return; } if (array.length == 1) { return; } // 以start为基准(pivot) 把小于start放入左边 大于start的放在右边 int pivotIndex = sortPivot(array, start, end); quickSort(array, start, pivotIndex - 1); quickSort(array, pivotIndex + 1, end); } /** * 一轮快排 * 以start为基准(pivot) 把小于start放入左边 大于start的放在右边 * @param array * @param start * @param end * @return 基准数最终放入的位置 */ public static int sortPivot(int[] array, int start, int end) { // 一般以start为基准(任意都可以) int pivot = start; int indexI = start; int indexJ = end; /* 从两端不断的向前走 先处理从后往前,直到遇到某个indexJ小于array[pivot] 再从start往后 直到遇到一个indexI array[indexI] >= array[pivot] 交换indexI indexJ ,不断循环直到indexI==indexJ ,交换pivot与indexI */ for (; indexJ > indexI; --indexJ) if (array[indexJ] < array[pivot]) { for (; indexI<=end;++indexI) { if (array[indexI] >= array[pivot]) { swap(array, indexI,indexJ); break; } } continue; } swap(array, pivot,indexI); return indexI; } /** * 交换数组内两个元素 * @param array * @param i * @param j */ public static void swap(int[] array, int i, int j) { if (i == j) { return; } int temp = array[i]; array[i] = array[j]; array[j] = temp; } public static void main(String[] args) { int[] array = {1,2,6,8,3,9,4}; quickSort(array); System.out.println("sorted array = " + Arrays.toString(array)); } }
4、插入排序
每次拿当前元素和前面元素比较,从后往前依次比较,遇到比其小的,则插入到其后面
第一轮 第2个数与第1个数比较,A2<A1,则交换
第二轮 第3个数与前面2个数比较,先比较A3 A2, 若A3<A2 , 则把A2往后移 ,继续与A1比较,若A1>A3,则A1也往后移一位
...
5、希尔排序
最早突破O(n^2)的一批算法,是插入排序的改进版,
算法思想:把数组不断分成多个组, 每组使用插入排序 (缩小增量序列)
一般的是以数组长度的一般作为间隔 ,相同间隔的就是一组 (希尔序列)
如数组长度为n ,一开始间隔为n/2 则A[0] A[n/2] A[0 + 2(n/2)] 为一组,这组按插入排序方法进行排序
然后不断间隔长度,间隔为(n/2)/2 再把相同组的元素排序。
如下 相同颜色的就是一组
6、归并排序
二分思想 分成2半 ,每半分别排序后,再把两个排序好的子序列从头依次比较,进行归并排序,这个过程不断递归
求逆序对的算法 可以基于归并排序的思想。
7、堆排序
利用大顶堆(小顶堆)的特性(即二叉树的根节点总是大于(或小于)其子节点,子树也满足)来进行排序
首先使用原始数组构建大顶堆,从堆中取根节点,就是最大的
余下的数重新构建大顶推 ,从堆中取根节点,就是次大的,依次这个过程。
构建大致过程:
a、拿树的最后的元素,即右下角的元素,移至堆顶
b. 堆顶元素跟左右子节点比较,把大的元素交换至堆顶,子树递归比较
8、计数排序
以空间换时间, 取数组中最大值,定义一个额外的数组,新数组大小就是原始数组中最大的值
一轮遍历,把原始数组对应元素放入新数组对应位置,对应位置计数加一A[i]++, 最后再从新数组中一一取出即可
限制:数组元素有明确范围 不能太大
9、桶排序
基于计数排序思想, 每个桶定义一段范围,把对应元素放入对应桶内,再对桶内的元素使用任一排序算法排序
10、基数排序
是按基数进行排序,如数字有0,1,2,...,9 共10个基数,字母有a,b,c,...,z共26个基数
从个位开始比较,把元素放入各个基数对应的队列中,再取出,就满足按个位比较是排序好的
再比较十位,以此类推,优先级不断增加
(这利用了基数排序的文档性,低优先级的排序,不会随着高优先级的排序而打乱)
三、复杂度对比
时间最快的排序算法:计数排序
稳定的排序算法:冒泡、插入、归并、计数、桶、基数
四、JDK Arrays.sort排序实现
List的sort最终也会调用Arrays.sort方法
JDK中数组的排序不是唯一的,插入排序、归并和快排都可能使用
参考: https://www.cnblogs.com/onepixel/articles/7674659.html
https://www.cnblogs.com/baichunyu/p/11935995.html