0
点赞
收藏
分享

微信扫一扫

线性排序--桶排序、计数、基数排序

凌得涂 2022-01-20 阅读 44

线性排序

今天的主题是线性排序,线性排序时间复杂度都是O(n),通常线性排序包括了桶排序、计数排序、基数排序。这些排序都是线性的,在排序的过程中不涉及元素的比较操作。

线性排序对数据要求很苛刻,所以今天主要是学习这些排序算法使用的场景。
例如:如何根据年龄给100万用户排序?
用归并、快排这些也可以搞定,但是这两个算法时间复杂度最低也是O(nlogn),而线性排序能达到O(n)。

桶排序

啥是桶排序呢,
其实就是设定几个有序的桶,然后把相应的数据放入桶里面,每个桶里面再单独利用排序算法进行排序,然后将桶里面的数据依序取出,组成的序列就是有序的了。

例如一组数据
【8,22,10,15,34,44,39,49】
那么可以分5个桶
桶1:0-9
桶2:10-19
桶3:20-29
桶4:30-39
桶5:40-49

桶排序时间复杂度

假如排序的数据有n个,均匀划到m个桶中,那么每个桶就有k=n/m个元素,然后每个桶的元素使用快速排序,时间复杂度是O(k * logk)
那么m个桶的时间复杂度就是O(m * k * logk),因为 k=n/m,所以整个桶排序的时间复杂度就是 O(n*log(n/m))。
当m无限接近于n的时候,这时候桶的时间复杂度就接近于O(n)。

桶排序的使用场景

桶排序很优秀,但是并不能代替之前的排序算法,因为桶排序是有很多前提条件的,

首先是要排序的数据要很容易划分为m个桶,且桶与桶之间有天然的大小顺序。这样桶内
排序完后不需要进行排序。

其次是各个桶之间的数据要分布均匀,如果一些桶数据很少,一些桶的数据很多,这样桶内排序的复杂度就不是常量问题了,且极端条件下所有数据在一个桶里面,就化为O(nlogn) 的排序算法了。

外部排序即数据存储在外部磁盘中,数据量很大,内存无法将全部数据加载到内存中。
例如有100G的数据要进行排序,按订单金额进行排序(假设都是整数),但是我们内存只有几百M,无法一次性加载完全部数据进内存。

那要如何借助桶排序进行排序呢
首先可以先对外部文件进行一次扫描,查看订单所在的数据范围。假设经过扫描后订单最小是1元,最大是10w元,我们将订单按金额划分到100个桶里面,第一个桶存放1-1000元,第二个桶1001-2000元、以此类推,且命令桶为00,01,02…99号桶

理想状态下1到1万均匀分布,订单划分到100个文件中,每个文件大概存放100m的订单数据。我们只需要将这100个文件以此放到内存中用快排将里面的数据进行排序,再按桶序号从小到大读取小文件即桶里面的数据再写入到一个文件中去,就能按金额从大到小排序了。

但是以上是理想状态,真实情况可能是1到10w之间的数据并不均匀分布,100g的数据是无法均匀分布到100个文件中去的。有可能某个数据区间的数据特别多,划分后的桶特别大,文件特别大,无法一次性读入内存中。

针对这些划分后还是特别大的文件,我们还可以对其继续划分,例如1-1000的订单比较多,那就对这个区间的数据进行再划分,例如划分为10个区间,1-100,101-200之类。直至所有的文件都能写入内存。

计数排序

计数排序是桶排序的一种特殊情况。例如要排序n个元素,最大值是K,那么就分为k个桶,每个桶里面放的元素都是一样相同的,没有桶内排序。
这样的排序只涉及到了遍历操作,所以时间复杂度是O(n).

计数排序的思想跟桶排序非常类似,但是跟桶排序不一样的是,计数排序是通过一个中间组进行计数的。
计数排序实现思想如下:
1.例如8个考生,分数在0-5之间,8人的成绩放在数组A中[2,5,3,0,2,3,0,3]

2.因为成绩是0-5,这里使用一个数据大小为6 的数组C来表示桶,其中下标对应是分数,C[6]里面存储的是考生该分数的人数,计数。C[6] = {2,0,2,3,0,1},代表分数0的有两人,分数1的0人,分数2的两人,分数3的三人,分数4的0人,分数5的1人。

3.然后对C[6]进行顺序求和,变成C[K]存储着小于等于分数K的考生个数
例如顺序求和后变成C[6] = {2,2,4,7,7,8} C[0]=2 代表分数小于等于0的有2人,C[1]=2代表分数小于等于1的有2人,C[2]=4代表分数小于等于 2的有4人,如此类推

4.创建一个结果表,结果表下坐标是人数,即R[0-7]。 这个时候开始扫描数组A,从后往前扫描,当扫描为3的时候,从数组C取出小标为3的值C[3]=7,即分数小于等于3的人有7个人。所以这个时候C[3]=7这个值要放在R[7-1]中,是R数组结果表的第七个元素。C[3]=7放入R结果表中后,小于等于3的值就只剩下6个了。下一个扫描到了0,以此类推当道R[C[0]-1],即R[1]中,此时小于等于0的就只剩下1个了。下一次再次扫描到了3,因为小于等于3的值只剩下6个了,即此时要放到R[C[3]-1-1]=R[5]。以此类推得到顺序数组R。

总的步骤为:
1.找出原数组最大值,最小值,然后创建一个大小为max-min+1的中间数组c。
2.让中间数组C存储从min到max,间隔为1的,各粒度的统计计数值。例如中间数组c[0]代表最小值min的个数,c[1]为最小值【min+1】这个数值的个数,
3.对中间数组进行顺序求和,
4.从后扫描原数组,放到结果数组

java代码为:

package main.java.java_test;

import java.util.Arrays;

/**
 * @discreption:
 * @author: Chen
 * @date: 2022年01月19日 2:04
 * @version: 2022年01月19日 admin
 */
public class 计数排序 {
    public static void main(String[] args) {
        int[] arr = {1,4,6,7,5,4,3,2,1,4,5,10,9,10,3};//112334
        sortcount(arr,10, 1);
        System.out.println(Arrays.toString(arr));
    }
    public static void sortcount(int[] arr, int max, int min){
        int len = arr.length;
        int[] c= new int[max - min +1]; //中间组以从arr的最小值到最大值的间隔为粒度,所以C组长度是max-min+1
        int sum = 0;
        for (int i = 0; i < len; ++i){
            c[arr[i] -min] ++;   //得到[2, 1, 2, 3, 2, 1, 1, 0, 1, 2],从最小值开始统计计数,例如c[0]=2代表等于最小值1有2人,c[1]=1代表等于最小+1=2的人有1人
        }
        for (int i = 0; i < c.length; ++i){  //将中间统计组进行顺序累加求和
            sum += c[i];
            c[i]=sum;  //得到[2, 3, 5, 8, 10, 11, 12, 12, 13, 15],例如c[0]=2代表比最小值小于或者等于最小值的有2个,c[1]=3,代表小于等于最小值+1的有3个
        }
        int[] r = new int[len]; //结果表大小跟源数据大小一样
        for (int i = len-1; i >=0; --i){ //从后扫描源数据,当arr[i] = n的时候,n应该放在结果表的从c[n-min]-1位置
            r[c[arr[i]-min]-1] = arr[i];
            c[arr[i]-min] --;
        }
        for (int i = 0 ; i < len; i++){
            arr[i] = r[i];
        }
    }
}

计数排序总结

计数排序因为要中间数组,所以只能是用于数据范围不大的情况下,如果数据范围k比排序的数据n大很多,就不适合用计数排序。且计数排序只能给非负整数排序,如果数据是其他类型,需要转换为非负整数才行。
例如数据范围是【-1000,1000】,那么可以加上1000后再进行计算。

基数排序

基数排序主要是通过元素的各个位的值,将元素放入桶中。
重点是各个位。
例如是手机号排序,手机号那么长,快排排序的话需要时间复杂度O(nlogn),要想更高效的话,因为号码太长,不能使用桶排序、计数排序,那就只能借助基数排序进行位的比较。
例如比较5组字符串,(hke),(iba),(hzg),(ikf),(hac),
这个要怎么比呢
1.先比较最后一位
2.再比较倒数第二位
3.再比较第一位
在这里插入图片描述
这里的比较排序要稳定算法,不稳定算法思路是不正确的。

如果单词的长度不一样,可以通过补0进行补到相同长度单词。

基数总结

基数排序对排序的数据要求是可以分割出位来比较的,且位之间有递进关系。如果a数据比B数据高位大,则低位就不需要比较。
除此之外还需要每一位的数据范围不能太大,要可以用线性算法排序,不然基数排序的时间复杂度无法做到O(n)

举报

相关推荐

0 条评论