十大排序算法代码详见:https://github.com/hanhanhanxu/TenSort
编写算法思路:由简单到复杂,先局部后整体,先粗糙后精细。
本文所有排序均有DataChecker验证。【验证的是普遍情况,多元素情况,不验证数组长度位0等少数情况】
验证思路:编写一个DataChecker类,编写check方法,接受一个AbstractSort类型的参数。check方法内部自动生成随机数组,并拷贝一份。调用AbstractSort的run方法将输入传入一个数组,然后再调用Arrays.sort方法传入另一个数组。最后一一比对两个数组是否完全一致,即可得知自定义排序算法是否正确排序。
AbstractSort为一个接口,规定void run(int[] arr);方法,所以自定义排序类需继承此接口并实现此方法。
最后调用System.out.println(DataChecker.check(new RadixSort()));
,输出true,即证明你的算法正确。
1、选择排序
最简单 最没用的排序算法 (时间复杂度:O(n平方),空间复杂度:1,不稳定)
思想:一遍一遍的找一个“最小值”,让他放在前面去。
优化:每次遍历除了找到最小值外,还再找一个最大值。最小值放前面, 最大值放后面。
最好情况也是O(n平方),也要进行许多次循环
基本不用,不稳。(2 3 2 1,第一次循环,将第一个2和1交换位置,那么两个2在排序前后的相对位置变化了)
代码:
1 | package Demo; |
##
2、冒泡排序
时间复杂度O(n平方),最好时间复杂度O(n),空间复杂度1,稳定
优化:加一个标识sign=false,当交换时,置为true,若一轮循环下来,sign仍为false,则return
基本不用,太慢。(两两比较,两两交换)
代码:
1 | package Demo; |
3、快速排序
思想:选择一个基准值,然后经过一轮循环将比此基准值大的值都放到基准值右边,将比此基准值小的值都放到左边(此时基准值已经在正确位置上了)。然后递归循环基准值左侧和右侧,直至不能循环(left==right),整个数组即为有序。
时间复杂度:O(nlogn),空间复杂度:O(logn),不稳定
不要求稳定性的情况,可以用快排。
代码:
1 | package Demo; |
##
插入排序稳定,希尔排序不稳定
4、插入排序
思想:将前面部分元素做为已排序元素,新来的元素插入到已排序元素中,从已排序元素中一个个比较,当找到合适位置时直接放入。由于内层循环没有全部遍历或比较,所以比冒泡和选择都要快一倍
时间复杂度:O(n平方),最好时间复杂度:O(n),空间复杂度1,稳定
这里是已经优化过的,没有优化的是交换而不是使用临时遍历current储存
适用于样本小且基本有序的数组。
代码:
1 | package Demo; |
5、希尔排序
改进的插入排序,新增了间隔:gap。
优点:在间隔比较大的时候,挪动的次数比较小;在间隔比较小的时候,挪动的距离又比较短。
由于是跳着排,所以不稳定,所以不太重要。
思想:原先的插入排序可看作是将间隔为1的元素互相比较,希尔排序增加一个间隔gap,先排序第
0, 0+gap, 0+gap+gap … 这些数,然后将间隔按规律缩小,继续排序,最后缩小gap至1,再进行排序。
时间复杂度第一个突破O(n平方)的排序算法,最好情况大致为O(n1.3次方),空间复杂度1,不稳定(原因:有间隔,跳着排)。
代码:
1 | package Demo; |
6、归并排序
思想:分治法,让已有序的子序列合并,得到完全有序的序列。【2路归并】,还有多路归并。
时间复杂度:O(nlogn),空间复杂度O(n),【申请了临时的数组】,稳定。
归并排序和选择排序的性能都不受输入数据的影响,但是比选择排序快很多,始终都是O(nlogn)的时间复杂度,但是需要额外的储存空间。
代码:
1 | package Demo; |
7、计数排序
第一代
使用情况:数组元素可预算最大值(或元素范围可知)且最大值不是很大(1000以下),全部整数。
思想:创建一个新数组,容量是待排序数组的最大值+1,并初始化为0。遍历待排序数组,将待排序中的元素作为索引值,将新数组中此索引值的元素+1;然后遍历新数组,当元素不为0时,循环将元素-1,并将此下标赋值给待排序数组(待排序数组从0开始:i=0; arr[i++] = temp[j])。
时间复杂度:O(n+k),两次遍历,一次待排序数组,一次新数组。(应该是2n吧?遍历新数组是for内还有while循环的,相当于又 遍历了n次)
空间复杂度:O(k) 。(申请了桶空间)
【输入的元素是 n 个 0到 k 之间的整数】
不稳定。其排序速度快于任何比较排序算法。
代码:
1 | package Demo; |
上述排序只适用于数字排序,且不稳定,因为是直接把数组下标当作了元素,桶里面是几,就直接拿出来几个索引作为元素。无法用于对象排序。
第二代
累加桶:将桶中元素从1位置开始,累加为buck[i]+buck[i-1],这样桶中元素-1就代表了某元素的实际位置。
倒序遍历原数组,将原数组元素作为索引值在桶中找到值后,这个值-1就是该元素应该出现的实际位置,直接赋值到该位置。且由于是倒序遍历,不会改变相同元素原有的相对顺序。
时间复杂度:O(n+k),原数组过滤两边,桶过滤两边,2(n+k),忽略常数项。
空间复杂度:O(n+k),用了额外的数组:桶,用了额外的数组:result。
稳定。
代码:
1 | package Demo; |
8、基数排序
多关键字排序,分治思想
思想:先将个位数用计数排序,再将十位数用计数排序,再将百位数用计数排序….
LSD MSD 低位优先,高位优先。
时间复杂度:O(n*k),空间复杂度:O(n+k),稳定。
代码:
1 | package Demo; |
9、桶排序
未完待续…
10、堆排序
未完待续…