总结
算法分类
十种常见排序算法可以分为两大类:
- 比较类排序:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序 都属于比较排序。在排序的最终结果里,元素之间的次序依赖于它们之间的比较。每个数都必须和其他数进行比较,才能确定自己的位置。
- 优势:适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
- 劣势:时间复杂度较高,为O(n2)或者O(nlogn),均高于线性时间O(n)
- 非比较类排序:计数排序、基数排序、桶排序则属于非比较排序。非比较排序是通过确定每个元素之前,应该有多少个元素来排序。针对数组arr,计算arr[i]之前有多少个元素,则唯一确定了arr[i]在排序后数组中的位置。
- 优势:非比较排序时间复杂度低,线性时间O(n)
- 劣势:但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。
还有一种分类方法,是看待排序的记录是否全部被放置在内存中(注:本文讨论的都是内排序):
- 内排序:所有待排序的记录都在内存中
- 外排序:由于记录太多,不能同时放置在内存中,整个排序过程需要内外存之间多次交换数据才能进行。
算法复杂度 + 空间复杂度
图片名词解释:
- 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,所以选择排序不是一个稳定的排序算法。
必看:参考文章
桶排序(请看这里)
- 相对于计数排序,桶排序和计数排序的差别就在于处理相同数据的差别上。计数排序假设输入数据都属于一个小区间内的整数,而桶排序则是假设输入是由一个随时过程产生的,区间不确定。
- 计算某元素应该放在哪个桶里:(num-min)/(max-min)表示这个数在(max-min)所占比重,再乘以桶的个数就得到对应桶的编号
- 当元素均匀分布时,时间复杂度可以优化到O(n)
请看文章:https://blog.csdn.net/u013521296/article/details/81625859?utm_source=blogxgwz5
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 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶
计数排序:每个桶只存储单一键值
桶排序:每个桶存储一定范围的数值