基数排序法
Posted 算法精解
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基数排序法相关的知识,希望对你有一定的参考价值。
简介
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
算法过程
最高位优先(Most Significant Digit first)法,简称MSD 法:
1)先按k1排序分组,将序列分成若干子序列,同一组序列的记录中,关键码k1 相等。
2)再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd 对各子组排序后。
3)再将各组连接起来,便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法一即是MSD 法。
最低位优先(Least Significant Digit first)法,简称LSD 法:
1) 先从kd开始排序,再对kd-1进行排序,依次重复,直到按k1排序分组分成最小的子序列后。
2) 最后将各个子序列连接起来,便可得到一个有序的序列,扑克牌按花色、面值排序中介绍的方法二即是LSD 法。
算法实现
基数排序Golang代码如下。
func RadixSort(arr[] int) {
max := arr[0]
for i := 1; i < len(arr); i++ {
if arr[i] > max {
max = arr[i]
}
}
// 关键字的个数
keysNum := 0
for max > 0 {
max /= 10
keysNum++
}
tmp, count := make([]int, len(arr)), make([]int, 10)
radix := 1
for i := 0; i < keysNum; i++ {
for j := 0; j < 10; j++ {
// 每次分配前清空计数器
count[j] = 0
}
for j := 0; j < len(arr); j++ {
// 统计每个桶中的记录数
k := (arr[j] / radix) % 10
count[k]++
}
for j := 1; j < 10; j++ {
count[j] = count[j - 1] + count[j]
}
for j := len(arr) - 1; j >= 0; j-- {
// 将所有桶中记录依次收集到tmp中
k := (arr[j] / radix) % 10
tmp[count[k] - 1] = arr[j]
count[k]--
}
copy(arr, tmp)
radix *= 10
}
}
func TestRadixSort(t *testing.T) {
rand.Seed(time.Now().Unix())
var arr []int
for i := 0; i < 10; i++ {
temp := rand.Intn(1000)
arr = append(arr, temp)
}
originSort, origin := append([]int{}, arr...), append([]int{}, arr...)
sort.Ints(originSort)
radixSort.RadixSort(arr)
if !reflect.DeepEqual(originSort, arr) {
t.Errorf("Got %v for input %v; expected %v", arr, origin, originSort)
}
}
测试结果如图1所示。
图1 测试结果
算法分析
时间复杂度
每一次关键字的桶分配都需要O(n)的时间复杂度,而且分配之后得到新的关键字序列又需要O(n)的时间复杂度。
假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2n) ,当然d要远远小于n,因此基本上还是线性级别的。
系数2可以省略,且无论数组是否有序,都需要从个位排到最大位数,所以时间复杂度始终为O(d*n) 。其中,n是数组长度,d是最大位数。
空间复杂度
基数排序的空间复杂度为O(n+k),其中k为桶的数量,需要分配n个数。
以上是关于基数排序法的主要内容,如果未能解决你的问题,请参考以下文章