桶排序、计数排序、基数排序--数据结构和算法之美--CH13

1. 概述

  本节介绍三种时间复杂度为 O ( n ) O(n) O(n)的排序算法,桶排序、计数排序和基数排序。这两种排序都是利用了分治思想,因为时间复杂度为线性的,因此这类排序算法也叫做线性排序。
  之所以能够做到 O ( n ) O(n) O(n),是因为这三种算法都是非基于比较的,但是对排序的数据要求苛刻,需要理解掌握其应用场景。

2. 桶排序

2.1 原理分析

  桶排序,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独排序。桶内排序完后,再把每个桶里的数据按照顺序依次取出,组成的序列便有序了。
如下图所示:

2.2 时间复杂度分析

  要排序数据有 n 个,把它们均匀地划分到 m 个桶内,每个桶有 k=n/m 个元素。每个桶内部使用快速排序,时间复杂度为 O(k * logk)。m 个桶排序的时间复杂度就是 O(m * k * logk),因为 k=n/m,所以整个桶排序的时间复杂度就是 O(n*log(n/m))。
  当桶的个数 m 接近数据个数 n 时,log(n/m) 就是一个非常小的常量,这个时候桶排序的时间复杂度接近 O(n)。

2.3 使用条件

  1. 要排序的数据需要很容易就能划分成m个桶,并且桶与桶之间有着天然的大小顺序。
  2. 数据在各个桶之间分布是均匀的。
  3. 数据范围K最好不要比数据规模n要大。

2.4 适用场景

  桶排序比较适合用在外部排序中,外部排序就是数据存储在外部磁盘且数据量大,但内存有限无法将整个数据全部加载到内存中。
  比如说我们有 10GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把 10GB 的数据都加载到内存中。这时候就可以使用桶排序思想,将订单金额划分区间,一个桶一个桶进行排序处理。

3. 计数排序

3.1 原理分析

  计数排序是桶排序的一种特殊情况。

  1. 当要排序的n个数据所处范围并不大时,比如最大值为k,则分成k个桶
  2. 每个桶内的数据值都是相同的,就省掉了桶内排序的时间。

3.2 思路步骤

  假设只有8个考生分数在0-5分之间,成绩存于数组A[8] = [2,5,3,0,2,3,0,3]。
  最后排序结果数组R[8] = [0,0,2,2,3,3,3,5]存储考生名次。那么如何得到R[8]的呢?
step1:使用C[6]代表大小为6的桶,小标对应分数,存储的是考生人数,只需遍历一边考生分数,就可以得到C[6] = [2,0,2,3,0,1]。
step2:对C[6]数组顺序求和则Csum[6]=[2,2,4,7,7,8]。
step3:从后到前扫描A,比如扫描到3时,可以从数组Csum中取出下标为3的值7。7表示包括自己在内,分数小于等于3的考生有7个,即3是数组R的第7个元素(也就是数组R中下标为6的位置)。
step4:将3放入数组R,小于等于3的元素就剩下6个,相应的C[3]要减1变成6。
重复上述过程,就能够完成排序。
如下图所示:

3.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];
  }
}

3.4 使用条件

  1. 只能用在数据范围不大的场景中,若数据范围k比要排序的数据n大很多,就不适合用计数排序;
  2. 计数排序只能给非负整数排序,其他类型需要在不改变相对大小情况下,转换为非负整数;
  3. 比如如果考试成绩精确到小数后一位,就需要将所有分数乘以10,转换为整数。

4. 基数排序

  比如一个场景,对于手机号码而言,由于11位号码范围很大显然不适合桶排序和计数排序,那么有没有时间复杂度为 O ( n ) O(n) O(n)的算法呢?

4.1 原理分析

  对于手机号而言,从前往后逐位比较,借助稳定排序算法。先按照最后一位来排序手机号码,然后,再按照倒数第二位重新排序,以此类推,最后按照第一位重新排序。经过 11 次排序之后,手机号码就都有序了。
  每一位排序可以使用桶排序或者计数排序完成。

4.2 时间复杂度

  对于每一位的排序,这时候就可以借助桶排序或者计数排序,时间复杂度为 O ( n ) O(n) O(n),假设有k位,则最终计数排序的时间复杂度为 k O ( n ) kO(n) kO(n),因此近似等于 O ( n ) O(n) O(n)

4.3 使用条件

  1. 要求数据可以分割独立的“位”来比较;
  2. 位之间由递进关系,如果a数据的高位比b数据大,那么剩下的地位就不用比较了;
  3. 每一位的数据范围不能太大,要可以用线性排序,否则基数排序的时间复杂度无法做到O(n)。

5. 解开篇答

  如何根据年龄给 100 万用户排序?
  使用计数排序思想,根据年龄给 100 万用户排序,就类似按照成绩给 50 万考生排序。我们假设年龄的范围最小 1 岁,最大不超过 120 岁。我们可以遍历这 100 万用户,根据年龄将其划分到这 120 个桶里,然后依次顺序遍历这 120 个桶中的元素。这样就得到了按照年龄排序的 100 万用户数据。

6. 课后思考

  假设我们现在需要对 D,a,F,B,c,A,z 这个字符串进行排序,要求将其中所有小写字母都排在大写字母的前面,但小写字母内部和大写字母内部不要求有序。比如经过排序之后为 a,c,z,D,F,B,A,这个如何来实现呢?如果字符串中存储的不仅有大小写字母,还有数字。要将小写字母的放到前面,大写字母放在最后,数字放在中间,不用排序算法,又该怎么解决呢?
  解决思路:利用桶排序思想,小写,大写,数字三个桶,遍历一遍,都放进去,然后再从桶中取出来就行了。相当于遍历了两遍,复杂度O(n)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
十大经典排序算法是指在计算机科学中常用的排序算法,它们分别是: 1. 冒泡排序(Bubble Sort):重复地比较相邻的两个元素,将较大的元素逐渐向右移动。 2. 选择排序(Selection Sort):每次从未排序的部分选择最小(或最大)的元素,并放在已排序的部分的末尾。 3. 插入排序(Insertion Sort):将未排序的元素逐个插入到已排序的部分中的正确位置。 4. 希尔排序(Shell Sort):将待排序的数组按照一定步长进行分组,对每组进行插入排序,逐渐减小步长。 5. 归并排序(Merge Sort):将待排序的数组递归地分成两半,对每一半进行排序,然后合并两个有序数组。 6. 快速排序(Quick Sort):选择一个基准元素,将数组划分为两部分,左边部分都小于基准,右边部分都大于基准,递归地对两部分进行排序。 7. 堆排序(Heap Sort):将待排序的数组构建成一个最大堆(或最小堆),然后依次取出堆顶元素并调整堆结构。 8. 计数排序(Counting Sort):统计数组中每个元素出现的次数,然后根据统计结果对元素进行排序。 9. 桶排序(Bucket Sort):将待排序的数组划分为多个桶,对每个桶中的元素进行排序,最后将桶中的元素按顺序合并。 10. 基数排序(Radix Sort):按照元素的位数,将待排序的数组从低位到高位进行排序。 以上是十大经典排序算法,每种算法都有其适用的场景和性能特点,选择合适的排序算法可以提高程序的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值