【数据结构与算法】->算法->排序(三)->线性排序->桶排序&计数排序&基数排序

Ⅰ 前言

再次把之前的表拿出来。排序算法就剩最后三个要讲啦。

在这里插入图片描述
前面两节可以跳转下面两篇文章去看👇

【数据结构与算法】->算法->排序(一)->冒泡排序&插入排序&选择排序

【数据结构与算法】->算法->排序(二)->归并排序&快速排序

这节我们来看三个时间复杂度为 O(n) 的排序算法:桶排序、计数排序、基数排序。由于这些排序算法的时间复杂度是线性的,所以我们把这类排序算法叫作线性排序(Linear sort)。之所以能做到线性的时间复杂度,主要原因是,这三个算法是非基于比较的排序算法,都不涉及元素之间的比较操作。

这篇文章我们主要来看这三个算法的适用场景。

Ⅱ 桶排序(Bucket Sort)

A. 原理

首先,我们来看桶排序。桶排序,顾名思义,会用到“桶”,核心思想就是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。

比如对一组金额在 0 ~ 50 之间的订单进行桶排序:

22, 5, 11, 41, 45, 26, 29, 10, 7, 8, 30, 27, 42, 43, 40

在这里插入图片描述
桶排序的时间复杂度为什么是 O(n) 呢?我们一起来分析一下。

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

看起来是很棒很优秀,但是桶排序对数据的要求是非常苛刻的。

首先,要排序的数据需要很容易就能划分成 m 个桶,并且,桶与桶之间有着天然的大小顺序。这样每个桶内的数据都排序完之后,桶与桶之间的数据不需要再进行排序。

其次,数据在各个桶之间的分布是比较均匀的。如果数据在经过桶的划分之后,有些桶的数据非常多,有些非常少,很不平均,那桶内数据排序的时间复杂度就不是常量级了。在极端情况下,如果数据都被划分到一个桶里,那就退化到 O(nlog2n) 的排序算法了。

B. 适用场景

桶排序比较适合用在外部排序中。

所谓的外部排序,就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。

比如说我们有 10GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把所有数据加载在内存里,这个时候就可以用桶排序。

我们可以先扫描一遍文件,看订单金额所处的数据范围。假设经过扫描之后我们得到,订单金额最小是 1 元,最大是 10 万元。我们将所有的订单根据金额划分到 100 个桶里,第一个桶我们存储金额在 1 元到 1000 元之内的订单;第二个桶存储金额在 1001 元到 2000 元之内的订单,以此类推。每一个桶对应一个文件,并且按照金额范围的大小顺序编号命名(00, 01, 02…99)。

理想的情况下,如果订单金额在 1 到 10 万之间均匀分布,那订单会被均匀划分到 100 个文件中,每个小文件存储大约 100MB 的订单数据,我们就可以将这 100 个小文件依次放到内存中,用快排来排序。等所有文件都排好序之后,我们只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。

不过,订单按照金额在 1 元到 10 万元之间并不一定是均匀分布的,所以 10GB 订单数据是无法均匀地被划分到 100 个文件中的。有可能某个金额区间的数据特别多,划分之后对应的文件就会很大,没法一次性读入内存,这时候应该怎么办呢?

针对这些划分之后还是比较大的文件,我们可以继续划分,比如,订单金额在 1 元到 1000 元之间比较多,我们就将这个区间继续划分为 10 个小区间,1 元 到 100 元, 101 元 到 200 元…901 元 到 1000 元。如果划分之后,101 元 到 200 元之间的订单还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。

C. 代码实现

package com.tyz.about_sort.core;

public class BucketSort {
   

	public BucketSort() {
   
	}
	
	public static void bucketSort(int[] arr, int bucketSize) {
   
		if (arr.length < 2) {
   
			return;
		}
		
		int minValue = arr[0];
		int maxValue = arr[1];
		//查询数据集中的最大值最小值
		for (int i = 0; i < arr.length; i++) {
   
			if (arr[i] < minValue) {
   
				minValue = arr[i];
			} else if (arr[i] > maxValue) {
   
				maxValue = arr[i];
			}
		}
		int bucketCount = (maxValue - minValue) / bucketSize + 1;
		int[][] buckets = new int[bucketCount][bucketSize];
		int[] indexArr = new int[bucketCount];	//记录每个桶里元素的
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值