前言
经典的线性排序算法有以下三种,这三种排序算法对数据集的要求比较严格,因此应用不广。
- 桶排序
- 计数排序
- 基数排序
桶排序
定义
将要排序的数据分到几个有序的桶里,每个桶里的数据再单独排序。桶内排完序后,再把桶内的数据按照顺序依次取出,组成的序列就是有序的。
数据集要求
- 数据可以很容易地划分成n个桶
- 数据在各个桶之间的分布是均匀的
这两点就就决定了桶排序的应用范围不广。
实现
//todo change
const oneBucketCapacity = 10
func BucketSort(nums []int) {
if len(nums) == 0 {
return
}
min, max := findMinAndMax(nums)
width := max - min
//创建桶
len := len(nums)
bucketSize := width / oneBucketCapacity
if width % oneBucketCapacity != 0 {
bucketSize += 1
}
buckets := make([][]int, bucketSize+1)
//数据分发到桶
for i := 0; i < len; i++ {
bucketIndex := (nums[i]-min)/oneBucketCapacity
if (nums[i]-min) % oneBucketCapacity != 0 {
bucketIndex += 1
}
buckets[bucketIndex] = append(buckets[bucketIndex], nums[i])
}
//桶内排序,依次回放
index := 0
for _, bucket := range buckets {
sort.Ints(bucket)
for _, val := range bucket {
nums[index] = val
index += 1
}
}
}
func findMinAndMax(nums []int) (int, int) {
min, max := nums[0], nums[0]
for i:=1; i<len(nums); i++ {
if min > nums[i] {
min = nums[i]
}
if max < nums[i] {
max = nums[i]
}
}
return min, max
}
时间复杂度
假设sort.Ints使用的是快速排序,n为数组长度,m为桶数量,并且数据分桶均匀,则桶中数据量为k=n/m,那么时间复杂度为:O(m* klogk) = O(m * n/m * log(n/m)) = O(nlog(n/m))
- 当m=n时,此时性能最好,时间复杂度为O(n)
- 当m=1时,此时性能最差,时间复杂度为O(nlogn)
空间复杂度
非原地排序:O(n)
稳定性
取决于桶内排序使用的算法,如果是快排或者选择排序,则是不稳定的,其他的就是稳定的。
应用场景
内存不够时的外部排序:比如内存100M,对10G的订单数据进行排序。
- 遍历订单文件,找到最大值最小值
- 评估分桶
- 再次遍历订单文件,入桶
- 如果某些桶文件 > 100 M, 继续进行分桶,回到2
- 所有桶文件都 <= 100M,依次加载到内存,使用快速排序,再写回到一个新文件
- 该新文件订单有序
计数排序
定义
当要排序的 n 个数据,所处的范围并不大的时候,比如最大值是 k,我们就可以把数据划分成 k 个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。
可以说,计数排序是桶排序的一种特例。
数据集要求
- 数据范围不大:比如高考分数、年龄这种范围
- 元素类型必须是非负整数:因为要映射到数组下标(数组下标>=0)
实现
func CountSort(nums []int) {
n := len(nums)
//1. 确定桶范围0-max
max := nums[0]
for i:=0; i<n; i++ {
if max < nums[i] {
max = nums[i]
}
}
//2. 初始化桶
bucket := make([]int, max+1)
//3. 计数
for i:=0; i<n; i++ {
bucket[nums[i]] += 1
}
//4. 排名
for i:=0; i<len(bucket)-1; i++ {
bucket[i+1] += bucket[i]
}
//5. 计数排序关键步骤
tmp := make([]int, n)
for i:=n-1; i>=0; i-- {
index := bucket[nums[i]]-1
tmp[index] = nums[i]
bucket[nums[i]] -= 1
}
//6. 赋值
for i:=0; i<n; i++ {
nums[i] = tmp[i]
}
}
时间复杂度
最好最坏平均都是:O(n)
空间复杂度
非原地排序:O(n)
稳定性
计数排序是稳定的。
应用场景
- 高考排名
- 对用户按年龄进行排名
基数排序
数据集要求
- 数据可以分割出独立的位进行比较
- 位之间有递进关系,如果高位数据已经比较出结果,低位无需再比较
- 每一位的数据范围不大,可以用线性算法来排序
如果使用到计数排序,那么多一条规矩:如果数据集是数字,必须是自然数
实现
func radixSort(nums []int) {
n := len(nums)
//1. 找到最大值
max := nums[0]
for i:=1; i<n; i++ {
if max < nums[i] {
max = nums[i]
}
}
//2. 计算位数
bl := getNumBitLength(max)
//3. 按位比较
for i:=0; i<bl; i++ {
sortByBit(nums, i)
}
}
//要求:时间复杂度O(n),且稳定
//非常适合使用计数排序
func sortByBit(nums []int, bitIndex int) {
n := len(nums)
bitNums := make([]int, n)
maxBit := 0
for i:=0; i<n; i++ {
bitNums[i] = getBitNum(nums[i], bitIndex)
if maxBit < bitNums[i] {
maxBit = bitNums[i]
}
}
bucket := make([]int, maxBit+1)
for i:=0; i<n; i++ {
bucket[bitNums[i]] += 1
}
for i:=0; i<maxBit; i++ {
bucket[i+1] += bucket[i]
}
tmp := make([]int, n)
for i:=n-1; i>=0; i-- {
index := bucket[bitNums[i]]-1
tmp[index] = nums[i]
bucket[bitNums[i]] -= 1
}
for i:=0; i<n; i++ {
nums[i] = tmp[i]
}
}
func getNumBitLength(num int) {
ans := 0
for num > 0 {
ans += 1
num /= 10
}
return ans
}
时间复杂度
最好最坏平均都是:O(n*k) = O(n)
k 即最大那个数据的位长度,n为数据量大小
空间复杂度
非原地排序:O(n)
稳定性
取决于sortByBit使用的排序算法,由于使用的计数排序,所以是稳定的。
应用场景
- 排序电话号码