提示:整个算法界,一共有十大排序算法,每一个算法都要熟悉,才算是算法入门
算法界的十大排序算法分别是:
选择排序、冒泡排序、插入排序、堆排序、希尔排序、归并排序、快速排序、桶排序、计数排序,基数排序。
(1)选择排序:10大排序算法之一:选择排序【不稳定】,一般不用选择排序的
(2)冒泡排序:10大排序算法之二:冒泡排序【稳定的】,但复杂度高,一般不用冒泡排序的
(3)插入排序:10大排序算法之三:插入排序【稳定的】,复杂度高,系统常在数据少时用插入排序。
(4)归并排序:10大排序算法之四:归并排序【稳定的】,复杂度中,系统常用归并排序
(5)快速排序:10大排序算法之五:快速排序【不稳定】,复杂度中,系统常用快速排序
(6)堆排序:10大排序算法之六:堆排序【不稳定】,复杂度中,不常用堆排序,堆结构更重要
如果你想进互联网大厂,至少这上面的其中最重要的8种,是必须熟练掌握的:
去掉希尔排序,希尔排序是一种改进的插入排序算法——所以只需要掌握插入排序
桶排序中最重要的就是计数排序和基数排序,都是桶的思想
根据算法复杂度低一点的,又稳定的
咱们可以最常用的算法实际上就四种:
插入排序(o(n^2))【当数据量小时,这个方法简单】【稳定】、
堆排序o(nlog(n))【不稳定】、
归并排序o(nlog(n))【稳定】,
快速排序o(nlog(n))(虽然快排不稳定,但是很多不需要稳定情况下,快排非常快)
因此,o(n)的桶排序很少用,除非面试官特别申明,否则都用比较排序。
归并排序是最常用的,复杂度低,而且稳定,达到了一个非常好的折中。
请你用计数排序算法,将arr排序为升序
示例:arr = 5 3 1 8 6 2 4
让其最终变为:arr= 1 2 3 4 5 6 8
计数,自然就是统计词频,词频范围不可太大,如果你统计的词频范围太大,这个计数就会太复杂耗时!
统计谁?
统计arr中国每个元素的词频
找到arr最大值,范围不要太大,就可以用计数排序
词频范围不大的统计:
(1)比如让你记录班上的学生年龄,把这些年龄排序放好。【0–100岁】
班上有6个学生,年龄分别是11 13 10 12 15 10
则排序后就是10 10 11 12 13 15
(2)比如让你把班上同学的高考成绩拿出来,然后按照成绩排序。【0–750分】
班上有5个学生,成绩分别是511 413 410 412 412 315
则排序后就是315 410 412 412 413 511
如果范围太大了,比如上万啥的,就算了吧!
什么是桶?
逻辑上是一个桶,装很多东西,比如数
(1)排序:arr=11 13 10 12 15 10
咱们这样做,将10 放一个桶里面,计数为2,将11放一个桶,计数1,……
这样的话
(2)令桶为count数组,下标i就是arr中的元素,arr中最大值是15,所以count的下标i从0–15
这样,遍历arr,遇到哪个数,就去count的下标那个数计数++,代表在i这桶里多加一个数i
——这个count干了啥呢,每个位置i,就是记录了arr中有几个i?
(3)然后,我们要排序
从每一个count的下标i开始,i=0到max=15,每一个数i,咱桶中有几个i呢?
count[i]有几个,咱就需要在arr中放几个i,因为count记录的就是arr中有几个i
这样的话,从左往右放,左边小,右边大,自然我们就能排序好arr了
这就是计数排序,利用桶的思想,让count统计arr中每个元素 i 出现了多少次?
然后按照count的每一个i位置的量【count[i]】,重新倒出来i,把i倒进arr【倒多少次?count[i]次】
这样就把arr排序好了。
显然,这个桶的思想,往往是统计词频,它不需要咱们想前面6大算法那样,去比较两个位置,然后交换他们。
故这是不基于比较的算法,很简单,也没法考验学生的算法水平。
除非考试时面试官特别申明,咱们就用基于比较的排序,别用桶的思想。
如果从左往右统计词频时,咱们让桶,先进先出,排序的时候就能做到相对位置不变,故计数排序是稳定的!
每次操作都是遍历一遍arr[i],至少o(n)复杂度
最后遍历count【范围不大的】
所以计数排序复杂度时o(n)
手撕代码:就是统计词频到桶count中,然后根据桶count倒出来,排序好了就
//复习计数排序:
public static void sortCountReview(int[] arr){
if (arr == null || arr.length < 2) return;
//找到arr最大值,范围不要太大
int max = Integer.MIN_VALUE;
int N = arr.length;
for (int i = 0; i < N; i++) {
max = Math.max(max, arr[i]);
}
//这个范围是count桶的下标范围
int[] count = new int[max + 1];//0--max
//扔桶
for (int i = 0; i < N; i++) {
count[arr[i]]++;
}
//倒出来,排序,i位置有几个,那就是倒几个
int index = 0;//倒出count每个位置i,i去覆盖arr,index做arr的下标索引
for (int i = 0; i <= max; i++) {
//桶里,有很多,就要连续倒
while (count[i] > 0) {
arr[index++] = i;//i就是我们桶代表的数,arr那来的
count[i]--;//倒一个减一个
}
}
//此时arr已经排序好了
}
测试一把:
//对数器之构建随机数组
public static int[] createArray(int arrSize, int maxValue){
int[] arr = new int[arrSize];
for (int i = 0; i < arrSize; i++) {
arr[i] = (int)(maxValue * Math.random());//0-N-1的随机数
}
return arr;
}
public static void checker(){
//生成检验数组
int[] arr = createArray(10000,750);
int[] arr2 = new int[arr.length];//赋值同样一个数组arr
for (int i = 0; i < arr.length; i++) {
arr2[i] = arr[i];//copy即可
}
int[] arr3 = new int[arr.length];//赋值同样一个数组arr
for (int i = 0; i < arr.length; i++) {
arr3[i] = arr[i];//copy即可
}
//绝对的正确方法——暴力方法,或系统函数,操作arr
Arrays.sort(arr);
//优化方法,操作arr2
countSort(arr2);
//复习优化方法,操作arr3
countSort(arr3);
//然后两个数组对位校验
boolean isSame = true;
for (int i = 0; i < arr.length; i++) {
if(arr[i] != arr2[i]) {
isSame = false;
break;//只要有一个不等,不必继续判断了
}
}
System.out.println(isSame == false ? "oops,wrong!" : "right!");
isSame = true;
for (int i = 0; i < arr3.length; i++) {
if(arr[i] != arr3[i]) {
isSame = false;
break;//只要有一个不等,不必继续判断了
}
}
System.out.println(isSame == false ? "oops,wrong!" : "right!");
}
public static void main(String[] args) {
checker();
}
结果:
right!
right!
提示:重要经验:
1)除非面试官特殊申明,否则你不要用这种非比较的排序算法,要用基于比较的排序算法。
2)计数排序要求,arr中的元素范围不要太大,太大就没法统计了。