一、例子演示

现有如下序列:{3,44,38,5,47,15,36,32,50},现在要利用基数排序算法对这9个元素进行从小到大的排序,怎么排呢?

  • 首先,排序的初始化状态如图1所示

                       

                                                                  图1:初始化状态

  • 第二,将这9个元素按个位分配到相应的位置上,如图2所示

                      

                                                                  图2:按个位分配后的结果

  • 第三,将第二步分配好的结果按顺序取出,因为是按个位排序的,所以取出来的元素一定是按个位有序的,如图3所示

                      

                                                                 图3:将元素按顺序取出

  • 第四,将元素按十位放入到相应的位置,上一步的数是按个位有序的,现在按十位放入相应的位置,放入之后,对于每个位置而言,都是大数在上面,小数在下面,如图4所示

                      

                                                                 图4:将元素按十位放入到相应的位置

  • 第五,因为是从小到大排序,将元素从左往右,从下到上依次取出,如图5所示

                      

                                                                 图5:最终结果

 

二、 算法思想

排序算法是一种非比较算法,其原理是将整数按每个位数分别比较。它利用了桶的思想。代码确实不怎么好写,还是需要仔细分析和钻研代码的

三、代码实现

#include<bits/stdc++.h>
using namespace std;

int temp[100];
int bucket[10];

int maxBit(int data[],int n)
{
	//行这些代码在求n个元素的最大值 
	int maxData = data[0];
	for(int i=1;i<n;i++)
	{
		if(maxData<data[i])
			maxData=data[i];
	}
	
	//这些代码在求最大值的位数是多少 
	int d=1;    //d用来计数最大值的位数,因为既然是一个数,肯定至少有1位,所以d初始化为1 
	while(maxData>=10)  //将最大值不断/10,计算位数 
	{
		maxData/=10;
		d++;
	}
	return d;
} 
void radixsort(int data[],int n)  //基数排序 
{
	int d = maxBit(data,n);  //求出最大位数
	int i,j,k;
	int radix = 1;
	for(i=1;i<=d;i++)   //进行d次排序
	{
	    for(j=0;j<10;j++)   //每次分配前清空计数器
		{
			bucket[j]=0;
		}
		for(j=0;j<n;j++)    //统计每个桶的元素个数 
		{
			k=(data[j]/radix)%10;
			bucket[k]++;
		}
		
		//关键代码1 
	    for(j = 1; j < 10; j++)
            bucket[j] = bucket[j - 1] + bucket[j]; 
       
       //关键代码2 
		for(j = n-1; j>=0; j--) 
        {
            k = (data[j] / radix) % 10;
            temp[bucket[k] - 1] = data[j];
            bucket[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = temp[j];
            
        radix = radix * 10;  //个位  -》 十位  -》百位 -》…… 
	} 
}

int main()
{
	int a[4]={2,1,34,4};   
	radixsort(a,4);         //a十待排序的数组 ,4是元素个数 
	for(int i=0;i<4;i++)
		cout<<temp[i]<<" ";
	return 0;
}

 

四、代码思路分析

    首先,因为基数排序是一个按位数比较的排序方法,因此在n个元素中,我们需要找出这n个元素的最大值(或者说算出这n个元素中最长位数),找出最大值是为了求n个元素中的最长位数,比如说:{2,1,34,4},这4个元素中的最长位数是2位(34就是两位),因为是按位排序,所以有x位,就要进行x次排序才能得到正确结果。所以,我们第一步应该写关于求最长位数的代码。我不妨把这个代码封装成一个函数,函数的名字叫做maxBit,这个函数的实现这里提供了两种方式:

第一种方式:求出最大值,将最大值不断/10,求最大值的位数,最大值的位数就是这n个元素的最长位数

int maxBit(int data[],int n)
{
	//21~27行这些代码在求n个元素的最大值 
	int maxData = data[0];
	for(int i=1;i<n;i++)
	{
		if(maxData<data[i])
			maxData=data[i];
	}
	
	//30~36这些代码在求最大值的位数是多少 
	int d=1;    //d用来计数最大值的位数,因为既然是一个数,肯定至少有1位,所以d初始化为1 
	while(maxData>=10)  //将最大值不断/10,计算位数 
	{
		maxData/=10;
		d++;
	}
	return d;
} 

第二种方式:遍历n个元素,对于某一个元素和p比较,如果比p大,就扩大p的值,同时位数+1

int maxBit(int data[],int n)
{
	//行这些代码在求n个元素的最大值 
	int maxData = data[0];
	for(int i=1;i<n;i++)
	{
		if(maxData<data[i])
			maxData=data[i];
	}
	
	//这些代码在求最大值的位数是多少 
	int d=1;    //d用来计数最大值的位数,因为既然是一个数,肯定至少有1位,所以d初始化为1 
	int p=10;   //辅助求最长位数
	for(int i=0;i<n;i++)
	{
		while(data[i]>=p)   //一旦发现n个元素中有大于等于p的,那么位数加1,p*10
		{
			   p*=10;
				d++;
	    }
	}
	return d;
} 

    其次,求出最大位数之后我们就可以开始排序了,x位的话根据咱们的例子演示需要排x次序,首先是按个位,然后按十位,然后按百位……以此类推,所以肯定需要一个x次的循环,这里不妨使用for循环,在for循环中我们需要做几件事情,因为我们是利用桶的思想来进行排序,要将元素放入到对应的不同的桶中,对于每一次排序我们都需要清空桶,也就是将0-9这10个桶全部置为0,个位排完置为0,重新计数,十位排完置为0,也重新计数,以此类推……,代码如下

for(j=0;j<10;j++)   
{
		bucket[j]=0;   //bucket代表桶,这是一个数组  定义为:int bucket[10];
} 

置为0之后,我们需要统计一下,当前这一轮排序,每一个桶中相应放进了多少个元素,代码如下:

for(j=0;j<n;j++)    //统计每个桶的元素个数 
{
    k=(data[j]/radix)%10;   //radix初始值为1,一开始是按个位排序,所以初始值为1
                            //data是待排序的数组,%10代表取得最末位的元素
    bucket[k]++;            //对应的编号的桶元素个数+1
}

之后,这一步很多人无法理解,需要认真看,这一步要做的事情就是(用语言不好描述,不如来看例子),我们还是以第一步举的{2,1,34,4}这4个元素为例,第一次是按个位排序的,按照这个桶分配的话就是下图

接下来就很关键了,我们要如何取出这些已经按个位排好序的数字呢?我们现在是4个元素,我们如何才能将这4个元素有序取出呢?,我现在不妨开辟一个空间为:int temp[4];,到底怎么取呢?你还记得我们在上一步统计了每一个桶里面放的元素个数吗?在这里具体表示就是如下图:

这个时候我们就要思考,如何建立一种映射关系让temp里面是元素是按个位有序排列的呢?,我们需要更新bucket数组的值,我们看下图:

我们还是用{2,1,34,4}这4个元素来说明这个问题,我们开辟了一个空间temp[4],第一次是用来存个位排序后的结果的,要想按正确的顺序存入temp,必须要更新bucket的值,bucket更新后的值如上图所示。现在2,1,34,4,这4个元素中的最后一个元素是4,说明个位是4,我们需要找到bucket[4]这个元素的值为4,我们已经统计过了以1,2,3为个位的元素有3个,而4对应的bucket[4]的数值为4,现在要将4存入到temp中,需要这样存储:temp[bucket[4]-1]=data[3]存储(这里data[4]={2,1,34,4}),bucket中的4在第一轮中代表的是个位,存完之后,需要将bucket[4]的值-1,那么bucket[4]的值此时为3。接下来就是倒2个元素34,它的个位也是4,于是,temp[bucket[4]-1=data[2],因为bucket[4]里面的值已经从4变为3了,所以temp[3-1]=data[2],所以temp[2]=34;接下来就是倒3个元素1了,他的个位是1,于是,temp[bucket[1]-1]=data[1],temp[1-1]=data[1],temp[0]=1;接下来就是倒4个元素,也就是第1个元素2了,2的个位是2,于是,temp[bucket[2]-1]=data[0],temp[1]=2;所以最终,temp中元素为{1,2,34,4},这个时候可以发现,temp中的元素已经按个位有序了。之后我们更新data数组的值,将temp中的元素有序复制给data数组。

  第三,我知道有人肯定想问,为什么2,1,34,4这4个元素我们在代码中要从后往前判断,而不从前往后呢?我们不妨往下看,上一轮按个位比完之后,接下来就是要按十位比较了。拿着{1,2,34,4},按十位比较,我们看下图:

  因为我们的最长位数为2,现在排序趟数进入了第二趟,和第一趟一样,先将bucket的数值全部清0,然后统计不同的桶中各自有多少个元素,之后为了能够方便放进temp中,更新桶的值,如下图:

我们拿着{1,2,34,4}这四个元素开始放值到temp中,一定要记得,此时我们是按十位存放,而不是个位了,我们按正常的顺序从后往前放,放的位置为,bucket[data[3]/radix%10]-1,此时radix已经由第一步的1变成10了,因为现在是按十位比较,于是,temp[bucket[data[3]/radix]-1]=temp[2]=4,然后bucket[data[3]/radix]的值-1,变为2了,然后读入倒二个元素34,34的十位为3,于是就是temp[bucket[3]-1]=temp[4-1]=temp[3]=34,接下来就是读入元素2了,2的十位为0,于是就是temp[bucket[0]-1]=temp[2-1]=temp[1]=2,此时bucket[0]的值需要-1,就变为1了,然后接下来读入的元素就是1了,1的十位为0,于是就是temp[bucket[0]-1]=temp[0]=1,所以最终temp的结果为{1,2,4,34};再将temp结果重新赋值给data数组,此时控制排序的循环结束。data数组已经按照从小到大排好序了。

这个时候我们思考,如果我们读入数据的时候不是倒序读入,而是正序读入,会发生什么情况呢?首先读入1,因为十位是0,所以temp[bucket[0]-1]=1;此时temp[2]=1;就出现问题了,1不可能放在temp[2]这个位置,因为这已经是最后一轮排序了。

倒序读取是为了保证同一个桶上面的数据按某一位在temp中升序,最后才能保证得到的序列是正确的

五、 总结

基数排序看着思路挺简单的,但是代码实现起来并没有那么简单,代码中有好几个点需要注意,比如:更新bucket的值,为什么需要更新bucket的值?再比如:为什么将值放入temp的时候元素需要倒序读入?这两个问题都是我们需要去思考的,如果这两个题搞明白,代码自然而然就会编写了。😄这一期我们就讲到这里啦!我们下期再见,若有问题🤭,欢迎加微信:Q1313135进行讨论沟通👈

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐