数据结构与算法之美 | 学习笔记10 —— 线性排序:桶排序、计数排序与基数排序

桶排序、计数排序和基数排序的时间复杂度都为 O ( n ) O(n) O(n),是线性的,因此叫做线性排序(Linear sort)。

一、桶排序(Bucket sort)

桶排序的思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序,最后将桶里的数据按照顺序依次取出。桶排序思想
时间复杂度分析:n个要排序的数据均匀划分到m个桶内,每个桶里有 k = n m k=\frac nm k=mn个元素,每个桶内使用快速排序,时间复杂度为 O ( k l o g k ) O(klogk) O(klogk),m个桶的时间复杂度为 O ( m ∗ k l o g k ) O(m*klogk) O(mklogk),因为 k = n m k=\frac nm k=mn,则 O ( n l o g ( n m ) ) O(nlog(\frac nm)) O(nlog(mn)),当m接近n时,整个桶排序的时间复杂度接近 O ( n ) O(n) O(n)
桶排序适用于外部排序场景,即数据存储在外部磁盘中,数据量较大,内存有限,无法将数据全部加载到内存中。例如按订单金额排序10G的数据,先经过扫描确定数据范围,再划分桶,对于一个桶内数量比较大的文件,再继续划分桶,直到所有的文件都能读入内存位置。

二、计数排序(Counting Sort)

计数排序是桶排序的一种特殊情况,当要排序的数据值范围不大时,每一个值作为一个桶。例如数组A[8]=[2,5,3,0,2,3,0,3],假设排序后数组为R[8],排序过程中分为6个桶C[6]:
举例
对C[6]数组顺序求和,得到的数组中C[k]存储小于等于分数k的考生个数:
举例
现在要做的是,从A[ ]中取数据,排序后放到R[ ]中。

  1. 扫描一遍数组,放入对应桶中,完成桶数组的顺序求和计算;
  2. 遍历A[ ]数组,扫描到第k个数据时,假设值为3,从桶数组C[ ]中取出C[3]=7,代表值小于当前数据的个数有7个,因此数据放入R[6]中,同时C[3]的值减一,循环此步骤。数组
// 计数排序,a是数组,n是数组大小。假设数组中存储的都是非负整数。
public void countingSort(int[] a, int n) {
  if (n <= 1) return;

  // 查找数组中数据的范围
  int max = a[0];
  for (int i = 1; i < n; ++i) {
    if (max < a[i]) {
      max = a[i];
    }
  }

  int[] c = new int[max + 1]; // 申请一个计数数组c,下标大小[0,max]
  for (int i = 0; i <= max; ++i) {
    c[i] = 0;
  }

  // 计算每个元素的个数,放入c中
  for (int i = 0; i < n; ++i) {
    c[a[i]]++;
  }

  // 依次累加
  for (int i = 1; i <= max; ++i) {
    c[i] = c[i-1] + c[i];
  }

  // 临时数组r,存储排序之后的结果
  int[] r = new int[n];
  // 计算排序的关键步骤,有点难理解
  for (int i = n - 1; i >= 0; --i) {
    int index = c[a[i]]-1;
    r[index] = a[i];
    c[a[i]]--;
  }

  // 将结果拷贝给a数组
  for (int i = 0; i < n; ++i) {
    a[i] = r[i];
  }
}

计数排序只用在数据范围不大的场景中,并都为非负整数

三、基数排序(Radix sort)

基数排序适用于:数据可以分割为独立的“位”,每一位数据范围不能太大。
例如对10万个手机号码排序,对于桶排序来说,范围太大,这种对相同位数排序的数据,我们使用基数排序。先按照最后一位来稳定排序手机号码,再按照倒数第二位重新排序,以此类推,最后按照第一位排序,最后全部的数据都是有序的。基数排序
其中的根据每一位来排序,可以用稳定的桶排序或计数排序,如果有k位手机号,则总时间复杂度为 O ( n ) O(n) O(n)
对于不等长的数据比较,例如英文单词,可以将所有文字补齐到同一长度,位数不够可以在后边补“0”(当待排序的数据每一位的ASCII值都大于0时)。

四、应用

:现在需要对[D, a, F, B, c, A, z]字符串排序,要求大写字母放前面,小写字母放后面,数字放中间。但大小写字母、数字内部不要求有序。
解答:用两个指针a、b:a指针从头开始往后遍历,遇到大写字母就停下,b从后往前遍历,遇到小写字母就停下,交换a、b指针对应的元素;重复如上过程,直到a、b指针相交。
对于小写字母放前面,数字放中间,大写字母放后面,可以先将数据分为小写字母和非小写字母两大类,进行如上交换后再在非小写字母区间内分为数字和大写字母做同样处理。(来自wecj)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值