桶排序、计数排序、基数排序算法

这3种排序通常都算作是一类排序算法,他们不像选择、冒泡、归并、快排之类的是基于比较来排序的。

1. 桶排序

以一个例子来说明:有一个数组为 [3, 4, 4, 7, 9, 1, 8, 1],我们能找到其中最小值为 min = 1,其中最大值为 max = 9,如果每个桶只装 2 种不同的数据(同种数据可以有多个),那么需要定义 (max - min) / 2 + 1 = 5 个桶,这5个桶按顺序只能装这样子的数据:[1, 2]、[3, 4]、 [5, 6]、 [7, 8]、 [9]。也就是说第 1 个桶只装 1 和 2,第 2 个桶只装 3 和 4,依次类推。遍历一遍数组,将数据分别装入到对应的桶中,这5个桶内的实际数据应该为:[1, 1]、[3, 4, 4]、[ ]、[7, 8]、[9],第 3 个桶是空桶。接着我们对每个桶排序,排好序之后,这 5 个桶按顺序合起来就是排好序的数据了。

代码如下(kotlin编写):

/**
 * @param array 待排序数组
 * @param bucketSize 桶的个数
 */
private fun bucketSort(array: Array<Int>, bucketSize: Int) {
    if (array.size <= 1 || bucketSize < 1)
        return
    //找出最大值、最小值
    var min = array[0]
    var max = array[0]
    for (item in array) {
        if (item > max) {
            max = item
        } else if (item < min) {
            min = item
        }
    }

    //定义桶
    var bucketList = ArrayList<ArrayList<Int>>(bucketSize)
    for (i in 0 until bucketSize) {
        bucketList.add(ArrayList())
    }

    //每个桶数字的间隔区间
    var section = (max - min + 1) / bucketSize
    if (section == 0)
        section = 1
    //遍历数据,放到对应桶中
    for (i in array.indices) {
        var bucketIndex = (array[i] - min) / section
        if (bucketIndex >= bucketList.size)
            bucketIndex = bucketList.size - 1
        var list = bucketList[bucketIndex]
        list.add(array[i])
    }

    for (i in bucketList.indices) {
        val itemList = bucketList[i]
        if (itemList.size <= 1)
            continue
        var arr: Array<Int> = itemList.toTypedArray()
        //数据少的话,直接使用插入排序,也可选择其他如快排,不会有太大区别
        insertSort(arr)
        itemList.clear()
        for (item in arr) {
            itemList.add(item)
        }
    }
    var i = 0
    for (itemList in bucketList) {
        for (item in itemList) {
            array[i++] = item
        }
    }
}

桶排序有很多局限性,它一般应用在特殊的场合下,桶大小的分配会影响到排序的性能。
桶排序是否稳定,要看桶内部的排序用什么算法。
其空间复杂度为:其时间复杂度与桶的个数有关,桶越多的情况下,其时间复杂度接近 O(n)

2. 计数排序

计数排序有 2 个基本条件:

  1. 计数排序不能有负数,如果有负数,可以统一加上一个正数之后,将所有数都转换为大于等于 0 的数,排好序之后再减去之前加的正数;
  2. 计数排序里的最大数不宜过大,否则会比较浪费空间;
计数排序过程图.png

具体逻辑,还是看代码比较清楚:

/**
 * 计数排序,待排序数组不能有负整数,如果有则需要将数据处理成非负整数
 */
fun countingSort(array: IntArray?) {
    array ?: return
    if (array.size <= 1)
        return
    //查找最大值
    var max = array[0]
    for (item in array) {
        if (max < item) max = item
    }

    //定义计数数组 [0, max],大小为 max + 1,
    var countingArr = Array(max + 1) { 0 }
    //遍历数组,统计每个数字的个数,例如数字 1 的个数存储在 countingArr[1] 中
    //统计结束之后,countingArr[i] 表示数字 i 在数组中的个数
    for (item in array) {
        countingArr[item]++
    }

    //计数数组依次累加,累加后对计数数组来说, countingArr[i] 存储的是 <= i 的数据的个数
    //其实这是构造一个前缀和数组
    for (i in 0 until countingArr.size - 1) {
        countingArr[i + 1] = countingArr[i + 1] + countingArr[i]
    }

    //申请一个临时数组,大小与待排序数组一样大
    var tmpArr = Array(array.size) { 0 }
    //将每个元素 num 放在新数组的第 countingArr[num] 项,每放一个元素就将 countingArr[num] 减去1。
    //假设countingArr[num] = c, 则表示原数组中 <= num 的元素个数有 c 个,我们想象一下如果将原数组排好序之后
    //则排好序的数组中索引为 c-1 的数肯定为 num
    for (num in array) {
        tmpArr[countingArr[num] - 1] = num
        countingArr[num]--
    }

    //从排好序的数组拷贝数据到原数组里
    for (i in tmpArr.indices) {
        array[i] = tmpArr[i]
    }
}

计数排序是稳定的
空间复杂度:O(n + k),k 为最大数的值
时间复杂度:O(n + k),k 为最大数的值,当 k 比较小时,其复杂度接近于 O(n)

3. 基数排序

通过数据的“位”来进行排序,可以从低位到高位进行排序,也可行从高位到低位进行排序,所有“位”都比较完毕之后,也就排好序了。如果“位”不足,可以补 0 或者其他。举个例子:
待排序数据列:178、50、34、1、8、251
比较个位:50、1、251、34、178、8
比较十位:1、8、34、50、251、178
比较百位:1、8、34、50、178、251
最后排序完成,代码如下:

/**
 * 基数排序
 */
fun radixSort(array: IntArray?) {
    array ?: return
    if (array.size <= 1)
        return
    //查找最大值
    var max = array[0]
    for (item in array) {
        if (max < item) max = item
    }
    //计算最大值的位数,例如 max = 148,则位数为 3
    var maxDigit = 0
    while (max != 0) {
        maxDigit++
        max /= 10
    }

    //每位的数字范围是[0-9],所以桶的大小设置成 10
    val bucketList = ArrayList<ArrayList<Int>>(10)
    for (i in 0 until 10) {
        bucketList.add(ArrayList())
    }

    //从低位到高位开始排序,这里采用桶排序
    var mod = 10
    var divide = 1
    //每位都进行一次桶排序
    for (i in 0 until maxDigit) {
        //遍历数据,根据"位"来判断数据放在哪个桶中
        for (j in array.indices) {
            val item = array[j]
            //取出数字的"位",例如 158 的个位为:(158 % 10) / 1 = 8
            //158 的十位为:(158 % 100) / 10 = 5
            //158 的百位为:(158 % 1000) / 100 = 1
            val num = (item % mod) / divide
            bucketList[num].add(item)
        }

        //从桶中取出数据放在原数组中,同一个桶中存储的是“位”相同的数据
        var index = 0
        for (list in bucketList) {
            for (item in list) {
                array[index++] = item
            }
            list.clear()
        }

        mod *= 10
        divide *= 10
    }
}

基数排序也是稳定的排序方式
空间复杂度:O(n)
时间复杂度:O(k * n),当 k 不大时,其复杂度接近于 O(n)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275

推荐阅读更多精彩内容