总结

算法分类

十种常见排序算法可以分为两大类:

  • 比较类排序:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序 都属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。
  • 优势:适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
  • 劣势:时间复杂度较高,为O(n2)或者O(nlogn),均高于线性时间O(n)
  • 非比较类排序:计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置。
  • 优势:非比较排序时间复杂度低,线性时间O(n)
  • 劣势:但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。

还有一种分类方法,是看待排序的记录是否全部被放置在内存中(注:本文讨论的都是内排序):

  • 内排序:所有待排序的记录都在内存中
  • 外排序:由于记录太多,不能同时放置在内存中,整个排序过程需要内外存之间多次交换数据才能进行。

 

算法 - 十大排序算法_数据

 

 

算法复杂度 + 空间复杂度

 

算法 - 十大排序算法_时间复杂度_02

图片名词解释:

  • n: 数据规模
  • k: “桶”的个数
  • In-place: 占用常数内存,不占用额外内存
  • Out-place: 占用额外内存
  • 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
  • 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
  • 空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。 

 

虽然同样是O(n2)的算法,但是平均时间来看,排序如下: 冒泡排序>选择排序>插入排序

 

稳定性

冒泡排序:稳定 -- 遇到相同的数组,选择不交换即可。

选择排序:不稳定 -- 在一趟选择,如果当前元素(5)比一个元素(2)小,而该小的元素(2)又出现在一个和当前元素相等 的元素(5')后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5' 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了2 8 5' 5 9,所以选择排序不是一个稳定的排序算法。

 

必看:参考文章

必看:十大经典排序算法最强总结(含JAVA代码实现)

 

桶排序(请看这里)

  • 相对于计数排序,桶排序和计数排序的差别就在于处理相同数据的差别上。计数排序假设输入数据都属于一个小区间内的整数,而桶排序则是假设输入是由一个随时过程产生的,区间不确定。
  • 计算某元素应该放在哪个桶里:(num-min)/(max-min)表示这个数在(max-min)所占比重,再乘以桶的个数就得到对应桶的编号
  • 当元素均匀分布时,时间复杂度可以优化到O(n)

 

 



import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

/*
①找到待排序的数组A的min和max
②桶的数量(A.length)
③遍历数组A,把每个元素放到对应的桶中
④对每个桶的内部进行排序
⑤遍历每个桶
*/
public class BucketSorting {
public static void main(String[] args) {
System.out.println("未排序前的A:");
int[]A={0,30,0,28,19,30,30,28,1,0,29,10,20,20,10,19,29};
System.out.println(Arrays.toString(A));
System.out.println("使用桶排序后的A:");
System.out.println(Arrays.toString(bucketsort(A)));
}

private static int[] bucketsort(int[] A) {
int max=Integer.MIN_VALUE;
int min=Integer.MAX_VALUE;
//找最值
for (int i = 0; i <A.length ; i++) {
max=Math.max(max,A[i]);
min=Math.min(min,A[i]);
}
//计算桶的数目
int buckets=A.length;
//把A中的元素分别放到对应的桶中,用ArrayList来实现桶
ArrayList<ArrayList<Integer>> bucketArray=new ArrayList<>(buckets);
for (int i = 0; i <buckets ; i++) {
bucketArray.add(new ArrayList<Integer>());

}
//遍历元素,并放到桶里面
for (int i = 0; i <A.length ; i++) {
int bucketsNum=getBucket(A[i],buckets,min,max);
bucketArray.get(bucketsNum).add(A[i]);

}
//对每个桶内部进行排序
for(int i = 0; i < bucketArray.size(); i++){
       //每个桶内部,这里选择了快排(比较排序,这点很疑惑。你也可以递归使用桶排序)... Collections.sort()-->Arrays.sort()-->快排
Collections.sort(bucketArray.get(i));
}
System.out.println(bucketArray.toString());
//合并每个桶
int count=0;
int[] result=new int[A.length];
for(int i = 0; i < bucketArray.size(); i++){

for (int j=0;j<bucketArray.get(i).size();j++){
result[count]=bucketArray.get(i).get(j);
count++;
}
}
return result;
}

//(num-min)/(max-min)表示这个数在(max-min)所占比重,
//再乘以桶的个数就得到对应桶的编号
public static int getBucket(int num,int buckets,int min,int max){
return (int)((num-min)*(buckets-1)/(max-min));
}


 

 

基数排序 vs 计数排序 vs 桶排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

基数排序:根据键值的每位数字来分配桶

计数排序:每个桶只存储单一键值

桶排序:每个桶存储一定范围的数值