基数排序是一种常见的算法,虽然在各个数据结构教材中都能看到,但在面试或笔试中却很少遇到。虽然知道基数排序的原理,但从未写过它的实现算法,最近看了后缀数组中用到了基数排序,又仔细研究了下,很有收获。
基数排序不同于其他的排序算法,它不是基于比较的算法。基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。它是一种稳定的排序算法。多关键字排序中有两种方法:最高位优先法(MSD)和最低位优先法(LSD)。通常用于对数的排序选择的是最低位优先法,即先对最次位关键字进行排序,再对高一位的关键字进行排序,以此类推。
算法的思想:类似于桶式排序,我们需要给待排序记录准备10个桶,为什么是10个??因为一个数的任何一位上,其数字大小都位于0~9之间,因此采用10个桶,桶的编号分别为0,1,2,3,4...9,对应待排序记录中每个数相应位的数值,基数排序也是因此而得名。我们先根据待排序记录的每个数的个位来决定让其加入哪个桶中。例如:待排序数组为
278 109 63 930 589 184 505 269 8 83
求取每个数的个位数,依次为:8 9 3 0 9 4 5 9 8 3
依照其个位数决定将其加入哪个桶中
[0] | 930 | |||
[1] | ||||
[2] | ||||
[3] | 63 | 83 | ||
[4] | 184 | |||
[5] | 505 | |||
[6] | ||||
[7] | ||||
[8] | 278 | 8 | ||
[9] | 109 | 589 | 269 |
此步骤即为教材中所说的分配,接下来就是要进行收集,依照桶的编号,将含有数据的桶中的数据依次取出,形成的新的数据记录为:
930 63 83 184 505 278 8 109 589 269
再对这个数组按照十分位进行分配进桶,收集,最后再按照百位进行分配进桶,收集。就可得到最终的排序结果。
算法实现:
//此函数的目的是取得数据每个位上的数值 //i为待取的数据 int getDigit(int i, int d) //d的值为1、2、3...,表示要求取的相应位的值,1表示求取个位, { //2表示十分位,类推 int val; while (d--) { val = i % 10; i /= 10; } return val; }
1 //基数排序算法的具体实现 2 void RadixSort(int *list, int begin, int end, int digit) 3 { 4 int radix = 10; //基数 5 int i = 0, j = 0; 6 int * count = new int[radix]; //存放各个桶的数据存放个数 7 int * bucket = new int[end - begin + 1]; 8 9 for (int d = 1; d <= digit; d++) 10 { 11 for ( i = 0; i < radix; i++) 12 count[i] = 0; //置空各个桶的统计数据 13 14 for (i = begin; i <= end; i++) 15 { 16 j = getDigit(list[i], d); 17 count[j]++; 18 } 19 20 for (i = 1; i < radix; i++) 21 count[i] = count[i] + count[i - 1]; //count[i]表示第i个桶的右边界索引 22 23 //将数据依次装入桶中,保证数据的稳定性,此步即为基数排序的分配 24 for (i = end; i >= begin; i--) 25 { 26 j = getDigit(list[i], d); 27 bucket[count[j] - 1] = list[i]; 28 count[j]--; 29 } 30 31 //基数排序的收集 32 //把桶中的数据再倒出来 33 for (i = begin, j = 0; i <= end; i++, j++) 34 list[i] = bucket[j]; 35 } 36 }
在算法实现中,我们用了两个数组:count[radix],bucket[end-begin+1]。radix为基数,即桶的个数,所以第一个for循环处count[i]表示第i个桶中装入了几个数据,而第二个for循环,则是为了标记在每个桶中,最后一个数据在整个数组中的右边界。例如:在上例中,第一个for循环结束后,count[0]=1,count[1]=count[2]=0,count[3]=2;在第二个for循环后,count[0]=1,count[1]=count[2]=1,count[3]=3,表示第三个桶中最后一个元素在数组中第三个位置。
bucket[]的大小与待排序数组大小一样,是在排序中做收集步骤用。
digit表示各个数据的最大位数。