(1)基本思想
我感觉可以把基数排序归类在“减治”的算法类别中,因为基数排序实际上每执行一次排序就能减少问题的规模(它是一种稳定的排序)。
我们在日常生活中最常使用的十进制数的大小比较其实就是基数排序最好的实例(具体见本文第2节),我们在比较两个2位数时,会先比较其十位的大小,然后比较个位大小。例如,53和56。这是基数排序的MSD(most significant digital)的情况。
当然,基数排序其实并没有用“比较”,基数排序和桶排序(bucket sort)都采用了“桶”作为每一步排序的存储单位。这些“桶”其实是排序好的。对于十进制数来说,一共只有0-9共10个桶。如果将乱序的数字(比如个位是多少就放入多少号的桶)放进去这些桶里面去,当按照桶的顺序取出来时,这些数字就是已排序的。也就是两个关键步骤:分配(distribute)和收集(collect)。这就是基数排序的核心思想,还是很巧妙的。而且这种分配和收集是常数时间的。所以基数排序能达到线性的时间复杂度。
另外,在字符串处理方面,由于字符的“桶”的个数也不是很大(英文字母只有26个),因此基数排序还是可行的。后缀数组的一种构造方法--倍增算法(Doubling algorithm)就是使用了基数排序。
(2)举例
下面这个十进制的基数排序充分体现其特点。注意:用蓝色标出的是我们放入“桶”的序号,位数不够的(如35)对应的位需要用0补齐。
数列內容 |
421 |
240 |
35 |
532 |
305 |
430 |
124 |
第1次分组 |
组0 |
240 430 |
组1 |
421 |
组2 |
532 |
组3 |
|
组4 |
124 |
组5 |
035 305 |
组6 |
|
组7 |
|
组8 |
|
组9 |
|
|
数列內容 |
240 |
430 |
421 |
532 |
124 |
35 |
305 |
|
第2次分组 |
组0 |
305 |
组1 |
|
组2 |
421 124 |
组3 |
430 532 035 |
组4 |
240 |
组5 |
|
组6 |
|
组7 |
|
组8 |
|
组9 |
|
|
数列內容 |
305 |
421 |
124 |
430 |
532 |
35 |
240 |
|
第3次分组 |
组0 |
035 |
组1 |
124 |
组2 |
240 |
组3 |
305 |
组4 |
421 430 |
组5 |
532 |
组6 |
|
组7 |
|
组8 |
|
组9 |
|
|
数列內容 |
35 |
124 |
240 |
305 |
421 |
430 |
532 |
|
这里可以找到基数排序的一个动画演示: http://sjjp.tjuci.edu.cn/sjjg/DataStructure/DS/web/flashhtml/jishupaixu.htm
(3)C++代码
////////////////////////////////////////////
// radix sort
///////////////////////////////////////////
typedef vector<int>
vi;
// Determine the number of digits of the largest number
int digits( vector<int> &
arr )
{
//find largest number in the array
int largest = arr[0], digits = 0
;
for ( size_t i = 1; i < arr.size(); ++
i )
if ( arr[ i ] >
largest )
largest =
arr[i];
//count the digits of the largest number
while ( largest != 0
)
{
++
digits;
largest /= 10
;
}
return
digits;
}
// Distribute elements into buckets based on specified digit
void distribute( vector<int> &arr, vector<vi> &buckets, int
digit )
{
int divisor = 10
;
//the bucket index
int
bucketID;
//e.g. when we need to get the hundred number, divisor must be 1000
for ( int i = 1; i < digit; ++
i )
divisor *= 10
;
for ( size_t k = 0; k < arr.size() ; ++
k )
{ /////////////key step//////////////////////
//e.g. (1234 % 1000 - 1234 % 100) / 100 to get the hundred digit "2"
bucketID = ( arr[k] % divisor - arr[k] % ( divisor / 10 ) ) / ( divisor / 10
);
buckets[bucketID].push_back(arr[k]);
}
}
// Return elements to original array
void collect( vector<int> &a, vector<vi> &
buckets )
{
int index = 0
;
for ( int i = 0; i < 10; ++
i )
for ( size_t j = 0; j < buckets[i].size() ; ++
j )
a[ index++ ] =
buckets[ i ][ j ];
}
//clear the buckets, restart
void clearBucket( vector<vi> &
buckets )
{
for ( int i = 0; i < 10; ++
i )
buckets[i].clear();
}
void radixSort( vector<int> &
arr )
{
//totalDigits represents the round to run distribute and collect
int totalDigits =
digits( arr );
vector<int>
temp;
//ten buckets
vector<vi> buckets( 10
, temp );
for ( int i = 1; i <= totalDigits; ++
i )
{
//distribute the array elements into buckets it belongs to
distribute( arr, buckets, i );
//collect elements from the buckets back into the array
collect( arr, buckets );
if ( i !=
totalDigits )
clearBucket( buckets ); //clear the buckets, restart
}
}
下面是调用的main函数:
int
main()
{
vector<int>
arr;
arr.push_back(19
);
arr.push_back(9
);
arr.push_back(30
);
arr.push_back(8
);
arr.push_back(6
);
arr.push_back(5
);
arr.push_back(21
);
cout << "Array elements in original order:\n"
;
for ( int i = 0; i < arr.size(); ++
i )
cout << arr[ i ]<<" "
;
cout << '\n'
;
radixSort( arr );
cout << "\nArray elements in sorted order:\n"
;
for ( size_t j = 0; j < arr.size(); ++
j )
cout << arr[ j ]<<" "
;
cout <<
endl;
return 0
;
}