基数排序(桶排序)——C语言实现

  本期我们讲解基数排序,基数排序讲完后,我们的常用排序算法专栏就已经讲完了,后续可能会出一些排序优化问题,以及排序算法结合C语言实战,比如迷宫求解停车场系统机房预约系统以及植物大战僵尸外挂等等小项目。本人从零开始学习云计算的专栏也在不断更新中,喜欢的小伙伴可以继续关注。

基数排序(桶排序)——C语言实现_第1张图片

文章目录

    • 一、基数排序
    • 二、排序思想
    • 三、动图演示
    • 四、图解
    • 五、代码实现(包含详细注释)
    • 六、问题解答
    • 七、致命缺陷(负数问题)

基数排序(桶排序)——C语言实现_第2张图片

一、基数排序

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或binsort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,在某些时候,基数排序法的效率高于其它的稳定性排序法。

二、排序思想

基数排序就是桶排序,也可以说是桶排序的一种,基数排序固定式九个桶,分别对应的是0~9十个数字。基数排序的基本思想是:先将待排序序列按个位数排好,然后按顺序复制回原数组;再按十位排序好,再按顺序复制回原数组;依次类推,按百位、千位,排序的趟数就是最大数的位数,比如千位数就要排四趟,百位数就要排3趟

三、动图演示

  我们先来看看基数排序的动图演示,一定要格外注意,把桶里面的数组放回去时,有些桶只有一个数,有些桶有多个数,而这些多个数的桶,他的数据放回去时,是按照放下来的顺序放回去的,也就是先下来的先上去这点很重要,后面要考。

基数排序(桶排序)——C语言实现_第3张图片

四、图解

  看了上面的动图,是不是感觉基数排序的思想及其简单,但是代码却是极为不好写,很复杂。因为我们得弄清楚,桶里面到底装的是什么,是数据本身吗?如果不是,数据本身存放在哪里?让我们接着往下看
基数排序(桶排序)——C语言实现_第4张图片
  接下来讲解一下上图是什么意思。因为我们是按数据的每一位进行排序,所以我们就得定义九个桶,根据数据的位值,将待排序的序列按位值丢进桶里面(但不是真的丢进去),上图中我们已经将数据按照各位丢进了各自的桶中,这时候我们只需要按顺序将这些数据再次放回原数组就可以了,但是,怎么放?这个顺序怎么找,很明显,我们得借助一个临时数组,这个临时数组和原数组等长,用来先存放这些要归还的数据。现在我们就需要将这些数从后往前放到临时数组(为什么要从后往前后面会剖析),到底要怎么放?拿上面的例子举例,我们可以提前知道,最后放进临时数组的顺序是这样的
在这里插入图片描述
  我们从49开始放,现在我们只需要知道49的下标是几,就能够把数据精准的放进去,那么49的下标6是怎么来的呢?很简单,因为49的前面有六个数啊,哈哈哈,是不是很简单?那么,这六个数的6怎么得来?

答案就是:先统计每个桶里面有多少个数,这是真正要放在桶里面的数据,然后前后累加,每个桶里面的元素就代表着累计加起来总共有多少个数。将这个数减去1就可以得到每个数该放到临时数组的下标。

五、代码实现(包含详细注释)

//基数排序
void RadixSort(int* arr, int n)
{
	//max为数组中最大值
	int max = arr[0];
	int base = 1;

	//找出数组中的最大值
	for (int i = 0; i < n; i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
	}
	//循环结束max就是数组最大值


	//临时存放数组元素的空间
	int* tmp = (int*)malloc(sizeof(int)*n);

	//循环次数为最大数的位数
	while (max / base > 0)
	{
		//定义十个桶,桶里面装的不是数据本身,而是每一轮排序对应(十、白、千...)位的个数
		//统计每个桶里面装几个数
		int bucket[10] = { 0 };
		for (int i = 0; i < n; i++)
		{
			//arr[i] / base % 10可以取到个位、十位、百位对应的数字
			bucket[arr[i] / base % 10]++;
		}
		//循环结束就已经统计好了本轮每个桶里面应该装几个数


		//将桶里面的元素依次累加起来,就可以知道元素存放在临时数组中的位置
		for (int i = 1; i < 10; i++)
		{
			bucket[i] += bucket[i - 1];
		}
		//循环结束现在桶中就存放的是每个元素应该存放到临时数组的位置


		//开始放数到临时数组tmp
		for (int i = n - 1; i >= 0; i--)
		{
			tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
			bucket[arr[i] / base % 10]--;
		}
		//不能从前往后放,因为这样会导致十位排好了个位又乱了,百位排好了十位又乱了
		/*for (int i = 0; i < n; i++)
		{
			tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
			bucket[arr[i] / base % 10]--;
		}*/

		//把临时数组里面的数拷贝回去
		for (int i = 0; i < n; i++)
		{
			arr[i] = tmp[i];
		}
		base *= 10;
	}
	free(tmp);
}

六、问题解答

  我们解答一下前面一直强调的问题,将数据放到临时数组时为什么要从后往前放。我们来看看,
基数排序(桶排序)——C语言实现_第5张图片
对于这个数组,我们按各位排完的顺序应该是如上图所示,对于四号桶,先排的34,后排的1234,因为34先被拿下来,但如果我们从前往后排,就会导致1234在前,34在后,因为我们代码是这样写的,i先扫到的元素会放到后面一个位置,bucket[arr[i] / base % 10]再减减,减减之后就是前一个位置了。意思就是,我们早就已经为四号桶准备了两个位置放置元素,谁先放,就会被放到下标大的那个位置。所以我们从后往前放,就能将本应该放到下标大的位置的1234放到它正确的位置上

	//开始放数到临时数组tmp
		for (int i = n - 1; i >= 0; i--)
		{
			tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
			bucket[arr[i] / base % 10]--;
		}

七、致命缺陷(负数问题)

  我们当前的程序只能排正数,一旦出现负数,程序就崩了。我们如何解决呢?

  答案就是:既然出现了负数,我们只需要将所有的数加上一个数,把所有的数变为非负数即可。那么这个数是什么呢?没错,就是数组里面最小数的绝对值,加上这个数后,排完序再将这个数给它减掉即可。

优化后的代码为

//基数排序
void RadixSort(int* arr, int n)
{
	//max为数组中最大最小值
	int max = arr[0];
	int min = arr[0];
	int base = 1;

	//找出数组中的最大值
	for (int i = 0; i < n; i++)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		if (arr[i] < min)
		{
			min = arr[i];
		}
	}
	//循环结束max就是数组最大最小值

	//循环将数组的元素全部变为正数
	//所有元素加上最小值的绝对值
	for (int i = 0; i < n; i++)
	{
		arr[i] += abs(min);
	}

	//临时存放数组元素的空间
	int* tmp = (int*)malloc(sizeof(int)*n);

	//循环次数为最大数的位数
	while (max / base > 0)
	{
		//定义十个桶,桶里面装的不是数据本身,而是每一轮排序对应(十、白、千...)位的个数
		//统计每个桶里面装几个数
		int bucket[10] = { 0 };
		for (int i = 0; i < n; i++)
		{
			//arr[i] / base % 10可以取到个位、十位、百位对应的数字
			bucket[arr[i] / base % 10]++;
		}
		//循环结束就已经统计好了本轮每个桶里面应该装几个数


		//将桶里面的元素依次累加起来,就可以知道元素存放在临时数组中的位置
		for (int i = 1; i < 10; i++)
		{
			bucket[i] += bucket[i - 1];
		}
		//循环结束现在桶中就存放的是每个元素应该存放到临时数组的位置


		//开始放数到临时数组tmp
		for (int i = n - 1; i >= 0; i--)
		{
			tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
			bucket[arr[i] / base % 10]--;
		}
		//不能从前往后放,因为这样会导致十位排好了个位又乱了,百位排好了十位又乱了
		/*for (int i = 0; i < n; i++)
		{
			tmp[bucket[arr[i] / base % 10] - 1] = arr[i];
			bucket[arr[i] / base % 10]--;
		}*/

		//把临时数组里面的数拷贝回去
		for (int i = 0; i < n; i++)
		{
			arr[i] = tmp[i];
		}
		base *= 10;
	}
	free(tmp);

	//还原原数组
	for (int i = 0; i < n; i++)
	{
		arr[i] -= abs(min);
	}
}

         回到顶部

基数排序(桶排序)——C语言实现_第6张图片

你可能感兴趣的:(手撕常用排序算法,c语言,排序算法,算法)