线性排序算法:计数排序

线性排序

时间复杂度是线性的,所以我们把这类排序算法叫作线性排序(Linear sort),桶排序、计数排序、基数排序就是常见的线性排序,之所以能做到线性,他们不是通过比较完成的。

桶排序(Bucket sort)

桶排序对于排序数据的要求是非常苛刻的。

核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。

如果要排序的数据有 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)。

为什么苛刻呢?

  1. 要排序的数据需要很容易就能划分成 m 个桶,并且,桶与桶之间有着天然的大小顺序。这样每个桶内的数据都排序完之后,桶与桶之间的数据不需要再进行排序。
  2. 数据在各个桶之间的分布是比较均匀的。如果数据经过桶的划分之后,有些桶里的数据非常多,有些非常少,很不平均,那桶内数据排序的时间复杂度就不是常量级了。在极端情况下,如果数据都被划分到一个桶里,那就退化为 O(nlogn) 的排序算法了。

桶排序一般适用于外部排序,开发中基本用不到

计数排序(Counting sort)

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

上面的结论是桶排序在开发中基本不用,为什么还要学习计数排序呢?

当要排序的 n 个数据,所处的范围并不大的时候,比如最大值是 k,我们就可以把数据划分成 k 个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。

高考查分数系统你还记得吗?我们查分数的时候,系统会显示我们的成绩以及所在省的排名。如果你所在的省有 50 万考生,如何通过成绩快速排序得出名次呢?考生的满分是 900 分,最小是 0 分,这个数据的范围很小,所以我们可以分成 901 个桶,对应分数从 0 分到 900 分。根据考生的成绩,我们将这 50 万考生划分到这 901 个桶里。桶内的数据都是分数相同的考生,所以并不需要再进行排序。我们只需要依次扫描每个桶,将桶内的考生依次输出到一个数组中,就实现了 50 万考生的排序。因为只涉及扫描遍历操作,所以时间复杂度是 O(n)。

计数排序的算法思想就是这么简单,跟桶排序非常类似,只是桶的大小粒度不一样。

它的核心点在于“计数”

通过一个例子来说明何为计数.

假设只有 8 个考生,分数在 0 到 5 分之间, 考生成绩如下:

记为:

//考生成绩
examineeScore = [1, 5, 2, 0, 1, 3, 0, 3]
复制代码

然后我们把考生的成绩按照分数分为6个桶, 数组下标表示分数

score = [size=6]
比如: socre = [2,1]

下标为0: 则0分的有两人
复制代码

此时我们遍历一遍考生成绩就可以得到成绩对应人个数的数组

for (i in examineeScore.indices) {
//     socre[examineeScore[i]] = socre[examineeScore[i]] + 1
     // 简化:
    socre[examineeScore[i]]++
    }
    
    res:[2, 2, 1, 2, 0, 1]
复制代码

个数有了,怎么进行排序呢?

这里有一个程式的技术点:

  1. 对 socre[6]数组顺序求和,socre[6]存储的数据就变成了下面这样子。socre[k]里存储小于等于分数 k 的考生个数。
[2, 4, 5, 7, 7, 8]
复制代码

具体计算为:

2 + 2 + 1 + 2 + 0 + 1
2。 4。  5。 7。 7。  8。
复制代码

比如socre[3] 里面的数据5,表示小于等于3分的人有5个 ([1, 5, 2, 0, 1, 3, 0, 3])

  1. 对带排序数组进行处理

比如,当扫描到 3 时,我们可以从数组 socre 中取出下标为 3 的值 7,也就是说,到目前为止,包括自己在内,分数小于等于 3 的考生有 7 个,也就是说 3 是排序后的数组中的第 7 个元素(也就是下标为 6 的位置)。当 3 放入到排序后的数组中后,小于等于 3 的元素就只剩下了 6 个了,所以相应的 socre[3]要减 1,变成 6。

执行流程如下:

注意红色箭头的变化,就是上述描述的(所以相应的 socre[3]要减 1,变成 6)部分

具体代码

private fun countSort(arr: IntArray, count: IntArray) {
    for (i in arr.indices) {
        count[arr[i]]++
    }
    var sum = 0
    for (i in count.indices) {
        print("${count[i]} \t")
        sum += count[i]
        count[i] = sum
    }
   
    println("计数数组 = ${Arrays.toString(count)}\n")
    for (i in arr.size - 1 downTo 0) {
        result[count[arr[i]] - 1] = arr[i]
        //2,2,4,7,7,8
        println(
            "数组下标为${arr[i]}的值${count[arr[i]]} --> 也就是分数小于等于${arr[i]}的人有${count[arr[i]]}个 --> ${
                Arrays.toString(
                    result
                )
            } \t"
        )
        count[arr[i]]--
        println("count 变为 ${Arrays.toString(count)}")
    }
}

复制代码

计数排序只能用在数据范围不大的场景中,如果数据范围 k 比要排序的数据 n 大很多,就不适合用计数排序了。而且,计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数.

作者:麦客奥德彪
链接:https://juejin.cn/post/7231766211615670329

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值