桶排序
基数排序之前,先说一下桶排序,桶排序很好理解,比如序列array = {3, 1, 2, 5, 6, 8},这个数组,进行桶排序就可以申请一个大小为10的vis数组,把vis[array[i]]++。在从1遍历到10,只要碰到vis[i] != 0 就直接输出vis[i]个 i,最后就是array排完序的样子了。这个是比较容易理解。
基数排序
上面桶排序的缺点太明显了,浪费空间不说,如果数字大一点的话,根本开不了那么大的数组。因此基数排序就对桶排序进行了一点点的改进。数字虽然可以特别大,但每一位的数字最多只有十种可能,我能不能根据数字每一位来进行排序呢?
赫尔曼·何乐礼就想到了解决办法,也就是今天的基数排序,基数排序是一个稳定的排序。先看一下过程,
对于序列 {3, 2, 44, 5, 6}来说,我们先按照最后一位来排序
变为了 {2, 3, 44, 5, 6}
然后是第二位来排序
变为了
{02, 03, 05, 06, 44}
神奇吧,就通过两个循环就做到了。
那么按照从低位到高位和从高位到低位排序结果一样不一样呢,为什么只能从低位到高位进行排序呢?
我们可以看 {3, 2, 44, 5, 6},如果直接按照高位排序,先从第二位排序,对于一个稳定的算法来说,它就变成了{03, 02, 05, 06, 44},接下来在按照第二位排序的话就变为了{02, 03, 44, 05, 06},因此说明了基数排序应该从低位到高位。
我们假设现在是根据高位来进行排序,那低位肯定是已经有序的了,如果高位相同的话,由于这个算法是稳定的,并且低位已经有序了,那么像45, 47这样的情况就还是45在前。所以前一位有序是为了下一位排序做铺垫的。如果第二位不同,那还看个啥第一位,直接排就行了,比如35, 47,3<5,所以35一定在47前面。
和桶排序的关系:
咱们前面说了基数排序是桶排序的改进,那为啥上面一点都没有提到桶排序呢?其实对于每一位进行排序的时候用的就是桶排序,每一位就十种数字,开一个大小为10的数组就够了。而且通过神奇的操作,还能把它在辅助数组中的位置定下来,我们代码中看。
代码
#include <iostream>
#include <cstring>
using namespace std;
int a[10000], temp[10000];
int n;
int find() //查找最大的位数是多少,也可以在输入的时候直接进行判断,就少一个n
{
int d = 1, p = 10;
for (int i=0; i<n; i++)
{
while (a[i] > p)
{
d++;
p*=10;
}
}
return d;
}
void slove() //基数排序
{
int k = find(), p = 1;
int buket[10]; //计算当前位有多少个i的数字
for (int i=0; i<k; i++)
{
memset(buket, 0, sizeof(buket));
memset(temp, 0, sizeof(temp));
for (int j=0; j<n; j++)
buket[(a[j] / p) % 10]++;
for (int j=1; j<10; j++) //计算出在temp数组中的位置
buket[j] += buket[j-1];
for (int j=n-1; j>=0; j--) //从n-1保证了当前位相等的情况下,低位大的数字先进temp的后面。不懂可修改for循环测试(3 1 5 44 5 2)
{
temp[buket[(a[j]/p)%10]-1] = a[j];
buket[(a[j]/p)%10]--;
}
for (int j=0; j<n; j++) //将辅助数组的内容放回原数组,好为了下一次计算做准备
a[j] = temp[j];
p*=10;
}
}
int main()
{
cin >> n;
for (int i=0; i<n; i++)
cin >> a[i];
slove();
for (int i=0; i<n; i++)
cout << a[i] << endl;
return 0;
}