【数据结构初阶】十一、归并排序(比较排序)的讲解和实现(递归版本 + 非递归版本 -- C语言实现)

=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

【数据结构初阶】十、快速排序(比较排序)讲解和实现
(三种递归快排版本 + 非递归快排版本 -- C语言实现)-CSDN博客

 =========================================================================

                     

常见排序算法的实现(续上期)

(详细解释在图片的注释中,代码分文件放下一标题处)
                 

四、归并排序

基本思想:

归并排序MERGE-SORT是建立在归并操作上的一种有效的排序算法
该算法是采用分治法Divide and Conquer)的一个非常典型的应用
已有序的子序列合并得到完全有序的序列
先使每个子序列有序再使子序列段间有序
两个有序表合并成一个有序表,称为二路归并

             

归并排序核心步骤:

                      

归并排序的特性总结:
  • 归并的缺点在于需要O(N)的空间复杂度
    归并排序的思考更多的是解决在磁盘中的外排序问题

                      
  • 该算法时间复杂度O(N*logN)
                         
  • 该算法空间复杂度O(N)
                        
  • 该算法稳定性稳定
                           

---------------------------------------------------------------------------------------------

                       

_MergeSort函数
--
归并排序函数(递归版本)的子函数(内部函数), 完成归并操作

  • 之后会涉及递归操作,这里先设置递归结束条件
    当前区间分割成无意义不合理区间返回上层递归
                        
  • 因为要将当前区间分为两部分,所以先获得当前区间的中间下标mid
                      
  • 通过mid对当前区间的左区间进行递归分割出新的左右区间
    同理,当前区间的右区间也进行递归分割出新的左右区间

                    
  • 递归分割完区间后递归返回时再进行归并操作
    分割出的区间元素归并排序后放到动态开辟的tmp数组
    归并完成后拷贝到原数组中
                 
  • 定义当前区间的左右区间范围
    定义一个tmp数组的起始下标index
                   
  • 使用while循环将分割出的区间元素归并到tmp数组中
                     
  • 最后将归并好的tmp数组拷贝回原数组中
图示:

该函数执行逻辑图:

                          

                          
---------------------------------------------------------------------------------------------

                    

MergeSort函数 -- 归并排序函数(递归版本)

  • 开辟动态数组tmp
    连续开辟和待排序数组对应类型和个数的动态空间
                   
  • 调用_MergeSort子函数进行归并操作
                    
  • 执行完归并排序后,释放开辟的动态空间tmp数组
图示:

该函数测试:

                          

                          
---------------------------------------------------------------------------------------------

                    

(难)MergeSortNonR函数 -- 归并排序函数(非递归版本)

  • 开辟动态数组tmp
    连续开辟和待排序数组对应类型和个数的动态空间
                   
  • 因为不能使用递归进行归并前分割操作来分割区间
    所以需要定义一个gap值
                    
  • gap默认为1,来进行一一归二”;
    之后gap*2进行二二归四”;再gap*2进行四四归八”……
    使用while循环循环进行几几归2*几”,
    完成本次while循环后,调整gap值gap*=2*gap),
    进行下次当前gap值几几归2*几操作
                     
  • 内嵌for循环找到要进行归并的两个小区间”,
    确保这两个区间范围不会越界或按需对其进行修正重点),
    然后进行归并操作拷贝操作

                       
  • 退出while循环后,完成非递归归并排序释放掉tmp数组
图示:

该函数测试:

                     

                     


                    

补充:计数排序(非比较排序)

             

本期博客再包含前两期博客中:
                    

【数据结构初阶】九、五种比较排序的讲解和实现
(直接插入 \ 希尔 \ 直接选择 \ 堆 \ 冒泡 -- C语言)-CSDN博客

                          

【数据结构初阶】十、快速排序(比较排序)讲解和实现
(三种递归快排版本 + 非递归快排版本 -- C语言实现)-CSDN博客

                     

我们总共了解了七种比较排序

比较排序需要通过比较元素之间的大小来进行排序的排序算法

                  

先通过下表来总结前面的七种比较排序:
排序算法
(比较排序)
平均情况
(时间复杂度)
最好情况
(时间复杂度)
最坏情况
(时间复杂度)
空间复杂度 稳定性
直接插入排序 O(N^2) O(N) O(N^2) O(1) 稳定
希尔排序 O(N*logN) ~ O(N^2) O(N^1.3) O(N^2) O(1) 不稳定
直接选择排序 O(N^2) O(N^2) O(N^2) O(1) 不稳定
堆排序 O(N*logN) O(N*logN) O(N*logN) O(1) 不稳定
冒泡排序 O(N^2) O(N) O(N^2) O(1) 稳定
快速排序 O(N*logN) O(N*logN) O(N^2) O(logN) ~ O(N) 不稳定
归并排序 O(N*logN) O(N*logN) O(N*logN) O(N) 稳定

                  

再补充了解一种非比较排序计数排序

基本思想:

计数排序又称为鸽巢原理是对哈希直接定址法的变形应用

                   

计数排序核心步骤:
  • 统计相同元素出现的次数
                      
  • 根据统计的结果将序列回收到原来的序列中
                     
归并排序的特性总结:
  • 计数排序数据范围集中时效率很高,但是适用范围及场景有限
                      
  • 该算法时间复杂度O(MAX(N,range))
                         
  • 该算法空间复杂度O(range)
                        

---------------------------------------------------------------------------------------------

                       

CountSort函数 -- 计数排序(鸽巢原理)函数

  • 先使用for循环找到a数组中的最大值max最小值min
                       
  • 通过max和min获得数组a的元素范围range
    开辟等大等数的动态统计数组count检查开辟是否成功
    开辟成功后对其进行初始化
                        
  • 使用for循环在统计数组count中统计对应下标元素出现次数
                        
  • 根据统计的结果序列回收到原来的序列数组
图示:

改函数执行逻辑图:

该函数测试:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

对应代码(续上期)

Sort.h -- 排序头文件

//归并排序函数(递归版本):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组的长度(n)
void MergeSort(int* a, int n);


//归并排序函数(非递归版本):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组的长度(n)
void MergeSortNonR(int* a, int n);


//计数排序(鸽巢原理)-- 非比较排序:
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组的长度(n)
void CountSort(int* a, int n);

            

            

---------------------------------------------------------------------------------------------

             

Sort.c -- 排序函数实现文件

//归并排序函数(递归版本)的子函数(内部函数):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收开辟的等大动态空间首元素地址(tmp)
//第三个参数:接收该数组起始位置下标(0)
//第四个参数:接收该数组尾部位置下标(数组元素个数-1)
//(函数名前加个“_”,表示为该函数的子函数)
void _MergeSort(int* a, int* tmp, int begin, int end) 
{
	//设置递归结束条件:
	if (end <= begin)
		//当前区间分割成无意义或不合理区间时:
	{
		//返回上层递归:
		return;
	}

	//先获得当前区间的中间下标mid:
	int mid = (end + begin) / 2;

	/*
	* 之后就可以将当前区间分成两个区间:
	* [begin, mid] [mid+1, end]
	* 如果左区间有序,右区间也有序就可以开展归并了
	* 
	* 可以先对当前有效的左区间进行递归,
	* 使其不断分割出新的左右区间;
	* 同理,也对当前右区间进行递归,不断分割出新的左右区间
	* 分割到区间范围: end <= begin 时(不合理、无意义区间),
	* 就返回递归不再分割
	* 
	* 递归分割完区间后,递归返回时再进行归并操作
	* 
	* 整个过程类似二叉树的后序遍历,左子树 -> 右子树 -> 根
	*								归并:
	* 左区间归并后有序 -> 右区间归并后有序 -> 归并排序后结果拷贝到原数组
	*/

	//对当前区间的左区间进行递归,分割出新左右区间:
	_MergeSort(a, tmp, begin, mid); //分割后左区间范围:[begin, mid]

	//对当前区间的右区间进行递归,分割出新左右区间:
	_MergeSort(a, tmp, mid+1, end); //分割后左区间范围:[mid+1, end]

	/*
	* 递归分割完区间后,递归返回时再进行归并操作,
	* 将分割出的区间元素归并到动态开辟的tmp数组中,
	* 归并完成后再拷贝到原数组中:
	*/

	//定义当前区间的左区间范围:
	int begin1 = begin; //当前左区间的左范围
	int end1 = mid; //当前左区间的右范围

	//定义当前区间的右区间范围:
	int begin2 = mid+1; //当前右区间的左范围
	int end2 = end; //当前右区间的右范围

	//再定义一个tmp数组的起始下标begin:
	int index = begin;

	//使用whlie循环将分割出的区间元素归并tmp数组中:
	while (begin1 <= end1 && begin2 <= end2)
		//当前区间的左右区间范围合理:
	{
		/*
		* 比较当前a数组中当前区间的左右区间元素大小,
		* 将a数组当前区间中的较小值尾插放入tmp数组中(循环)
		*/

		if (a[begin1] <= a[begin2])
			//如果当前区间的左区间元素小于等于其右区间元素:
		{
			tmp[index++] = a[begin1++];
			//将此时a数组中的当前区间的左区间元素尾插进tmp数组
			//放入后++调整位置
		}
		else
			//当前区间的右区间元素小于其左区间元素:
		{
			tmp[index++] = a[begin2++];
			//将此时a数组中的当前区间的右区间元素尾插进tmp数组
			//放入后++调整位置
		}
	}

	/*
	* 当循环结束时,不知道是左右哪个区间不合理结束了,
	* 我们假设循环是左区间合理,右区间不合理而结束,
	* 那么此时的左区间就还有元素没尾插到tmp数组中,
	* 所以要将其剩下的元素再尾插到tmp数组中(右区间同理):
	*/

	//假设循环是左区间合理,右区间不合理而结束:
	while (begin1 <= end1)
		//当前左区间还合理(还有元素):
	{
		//将其剩下的元素尾插进tmp数组中:
		tmp[index++] = a[begin1++];
	}

	//如果循环是右区间合理,左区间不合理而结束:
	while (begin2 <= end2)
		//当前右区间还合理(还有元素):
	{
		//将其剩下的元素尾插进tmp数组中:
		tmp[index++] = a[begin2++];
	}

	/*
	* 到此就将元素都归并排序到了tmp数组中,
	* 最后还有将tmp数组拷贝到原数组中:
	*/

	//使用memcpy库函数将归并好的tmp数组拷贝回原数组:
	memcpy(a + begin, tmp + begin, (end - begin + 1)*sizeof(int));
	//	   (目的地) (被拷贝位置)	       (拷贝数据大小)
	// end-begin+1, 即元素个数,因为区间左闭右闭所以还要+1
}


//归并排序函数(递归版本):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组的长度(n)
void MergeSort(int* a, int n)
{
	//开辟动态数组tmp:
	//连续开辟和待排序数组对应类型和个数的动态空间:
	int* tmp = (int*)malloc(sizeof(int) * n);
	//检查开辟动态空间是否成功:
	if (tmp == NULL)
		//返回NULL,说明开辟失败:
	{
		//打印错误信息:
		perror("malloc fail");
		//返回结束当前函数
		return;
	}
	
	/*
	* 之后要进行归并排序,需要使用递归进行,
	* 而上面代码是开辟动态空间,
	* 如果在本函数中使用递归进行归并排序操作,
	* 不断递归会不断地开辟动态空间,
	* 所以接下来的归并排序操作可以交给子函数进行:
	*/

	//调用子函数进行归并排序操作:
	_MergeSort(a, tmp, 0, n - 1);
	//第一个参数:接收要排序的数组首元素地址(a)
	//第二个参数:接收开辟的等大动态空间首元素地址(tmp)
	//第三个参数:接收该数组起始位置下标(0)
	//第四个参数:接收该数组尾部位置下标(数组元素个数-1)
	//(函数名前加个“_”,表示为该函数的子函数)

	//执行完归并排序后,释放开辟的动态空间:
	free(tmp);
}
//空间复杂度:O(N)
//时间复杂度:O(N*logN)



//归并排序函数(非递归版本):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组的长度(n)
void MergeSortNonR(int* a, int n)
{
	/*
	*					思路:
	* 
	* 递归版本的归并排序是先通过递归将原数组不断分割,
	* 分割到无法分割后再进行归并排序操作返回到原数组,
	* 
	* 而非递归版本的归并排序要在一开始就找到原数组的
	* 最小区间,对两个最小区间进行归并操作,
	* 排序完成一个由两个最小区间组成“大区间”(已有序),
	* 最小区间都组成“大区间”后,
	* 再对两个“大区间”进行归并操作,组成一个“大大区间”(已有序)
	*				 (以此类推)
	* 其中组成“大区间”的“小区间”需要定义一个gap值来控制
	*(此过程就是递归版本中开始返回进行归并排序的过程)
	*(两区间有序了才能进行归并合成一个有序的大区间)
	*/

	//开辟动态数组tmp:
	//连续开辟和待排序数组对应类型和个数的动态空间:
	int* tmp = (int*)malloc(sizeof(int) * n);
	//检查开辟动态空间是否成功:
	if (tmp == NULL)
		//返回NULL,说明开辟失败:
	{
		//打印错误信息:
		perror("malloc fail");
		//返回结束当前函数
		return;
	}

	//gap = 1时,“一一归二”(11归并):
	/*
	*			“一一归二”:
	* 控制一个区间的左右区间都各只有一个元素,
	* 再比较左右区间中元素(此时都只有一个元素)大小,
	* 进行归并操作将这左右区间归并为一个有两个元素的区间(已排序)
	* 
	* gap = 2时,“二二归四”(22归并)同理;
	* gap = 4时,“四四归八”(44归并)同理。
	*	(gap每次调整都是二倍的关系)
	*/

	int gap = 1; //定义gap值

	//最外层while循环控制“几几归”:
	while (gap < n)
	//当“几”>数组长度时,归并排序完成
	{
		for (int i = 0; i < n; i += 2 * gap)
			/*
			*		 循环变量修正表达式:i += 2*gap
			* 使用该循环变量修正表达式是为了确保
			* 除了第一次之后的定义左右区间能够定义正确,
			* 左右区间被归并成一个区间后,就不需要再被进行归并了,
			* 所以要调整循环变量修正表达式,保证一个区间不会被多次归并
			*/
		{
			/*
			* gap为1,那么就需要在当前区间中,
			* 从左往右依次以2个元素定义一个区间,
			* 将该区间再分割为左右两个区间,
			* 所以左右区间都各只有1个元素;
			*
			* gap为2 同理,在当前区间中,
			* 从左往右依次以4个元素定义一个区间,
			* 将该区间再分割为左右两个区间,
			* 所以左右区间都各只有2个元素;
			*
			*	    (以此类推)
			*/

			//定义左区间的左范围(下标):
			int begin1 = i;
			//定义左区间的右范围(下标):
			int end1 = i + gap - 1;

			//定义右区间的左范围(下标):
			int begin2 = i + gap;
			//定义右区间的右范围(下标):
			int end2 = i + 2 * gap - 1;

			//[begin1, end1] [begin2, end2]

			//判断以当前gap值划分的“左右区间”是否合理(会不会越界):
			if (end1 >= n || begin2 >= n)
				/*
				*		    左右区间:
				* [begin1, end1] [begin2, end2]
				* 
				*				 end1 >= n
				* 通过分析,随着gap的增大,begin1不可能会越界,
				* 而从end1开始后就有可能会越界了,
				* 如果end1都已经越界了的话,那就没必要再进行归并了,
				* 
				*				begin2 >= n
				* 也有可能“左区间”正常,“右区间”越界了,导致
				* “几几归2*几”的第二个“几(右区间)”没了,
				* 导致无法将两个“小区间”归并成“大区间”
				* 
				* (如果“当前右区间”左右范围都不合理,
				*	  这一组就不用进行归并操作了)
				*/
			{
				//如果满足其中一种情况就无法进行归并操作了,
				//直接break跳出内嵌for循环:
				break; 
			}

			if (end2 >= n)
				/*
				* 如果只有"右区间"的右范围越界(end2 >= n)的话,
				* 那么说明“右区间”还有元素可以和“左区间”进行归并,
				* 所以要对此时的“右区间”的右范围进行修正:
				*/
			{
				end2 = n - 1; //修正到数组的尾元素下标位置
			}


			//打印查看归并时“几几归2*几”的过程:
			printf("当前 “%d%d归%d” 的区间:> ", gap, gap, 2 * gap);
			printf("[%d,%d]  [%d,%d]\n", begin1, end1, begin2, end2);

		/*
		* 通过gap分出“想要的左右两个区间(a数组的)”后,
		* 对这左右两个区间进行和递归版本相同的归并操作,
		* 比较这两个区间后,将“较小区间”尾插进tmp数组,
		* 这两个区间在tmp数组中变得有序,将其再拷贝回原数组
		*/

		//再定义一个tmp数组的起始下标begin:
			int index = i;

			//使用whlie循环将分割出的区间元素归并tmp数组中:
			while (begin1 <= end1 && begin2 <= end2)
				//当前区间的左右区间范围合理:
			{
				/*
				* 比较当前a数组中当前区间的左右区间元素大小,
				* 将a数组当前区间中的较小值尾插放入tmp数组中(循环)
				*/

				if (a[begin1] < a[begin2])
					//如果当前区间的左区间元素小于其右区间元素:
				{
					tmp[index++] = a[begin1++];
					//将此时a数组中的当前区间的左区间元素尾插进tmp数组
					//放入后++调整位置
				}
				else
					//当前区间的右区间元素小于其左区间元素:
				{
					tmp[index++] = a[begin2++];
					//将此时a数组中的当前区间的右区间元素尾插进tmp数组
					//放入后++调整位置
				}
			}

			/*
			* 当循环结束时,不知道是左右哪个区间不合理结束了,
			* 我们假设循环是左区间合理,右区间不合理而结束,
			* 那么此时的左区间就还有元素没尾插到tmp数组中,
			* 所以要将其剩下的元素再尾插到tmp数组中(右区间同理):
			*/

			//假设循环是左区间合理,右区间不合理而结束:
			while (begin1 <= end1)
				//当前左区间还合理(还有元素):
			{
				//将其剩下的元素尾插进tmp数组中:
				tmp[index++] = a[begin1++];
			}

			//如果循环是右区间合理,左区间不合理而结束:
			while (begin2 <= end2)
				//当前右区间还合理(还有元素):
			{
				//将其剩下的元素尾插进tmp数组中:
				tmp[index++] = a[begin2++];
			}

			//使用memcpy库函数将归并好的tmp数组拷贝回原数组:
			//(放在内嵌for循环中,归并一组就拷贝一组,没归并就不拷贝了)
			memcpy(a + i, tmp + i, (end2-i+1) * sizeof(int));
			//	(目的地)(被拷贝位置)   (拷贝数据大小)
			/*
			* end2-i+1, 即元素个数,这里“-i”本来应该是begin1,
			* 但是begin1在进行归并操作时会被++,所以应该是“-i”,
			* 即减去begin1的起始值i,再“+1”是因为“数组尾元素下标+1”
			* 才是数组的元素个数
			*/	
		
		} //内嵌for循环

		//打印当前一组“几几归2*几”对应的所有区间后,
		//进行换行,准备下次“几几归2*几”:
		printf("\n");

		//		“几几归2*几”:
		//“一一归二”后,进行“二二归四”,
		//“二二归四”后,进行“四四归八”,
		//(以此类推,直到“几”>数组长度)
		gap *= 2 ;

		/*
		* 执行到此,以gap=1为准的归并操作就完成了,
		* 但是当前只能处理有2^n个元素的数组,
		* (如果不对归并的“左右区间”进行限定的话)
		* 不然归并会出各种问题,所以要在归并开始前进行判断,
		* 看用于归并的“左右区间”会不会越界
		*/

	} //最外层while循环
	
	//执行完归并排序后,释放开辟的动态空间:
	free(tmp);
}
//时间复杂度:O(N*logN)
//空间复杂度:O(N)
//(时间和空间复杂度都和递归版本相同)



//计数排序(鸽巢原理)-- 非比较排序:
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组的长度(n)
void CountSort(int* a, int n)
{
	/*
	*				思路:
	* 1. 统计相同元素出现次数
	* 2. 根据统计的结果将序列回收到原来的序列中
	* (适合对数据范围相对集中的数组进行排序)
	*		    (只适用于整型)
	* 
	* 简单描述:找出被排序数组a中最大元素(max)和最小元素(min),
	* 创建一个以max为尾元素下标的数组count,
	* 这样被排序数组a中的元素就都能在count数组的下标中找到,
	* 而且count数组的下标是有序的,
	* count数组全部下标对应元素一开始都为0,
	* 
	* 1. 统计相同元素出现次数:
	* 假设被排序数组a为:[2,1,5,2,4] ,遍历该数组,
	* a数组中有元素:2 ,
	* 那么就在count数组下标为2的位置元素++ (0变成1)
	* 有元素:1,在count数组下标为1的位置元素++ (0变成1)
	* 有元素:5,在count数组下标为5的位置元素++ (0变成1)
	* 有元素:2,在count数组下标为2的位置元素++ (1变成2)
	* 有元素:4,在count数组下标为4的位置元素++ (0变成1)
	* 
	* 2. 根据统计的结果将序列回收到原来的序列中:
	* 根据第1步中count数组的统计,count数组为:
	*
	*		0		1		2		0		1		1
	*   (下标0)(下标1)(下标2)(下标3)(下标4)(下标5)
	*
	* 这时遍历count数组,按以下规律覆盖写到原数组a中:
	*
	*	0有0个,1有1个,2有2个,3有0个,4有1个,5有1个 
	*			(0个的话就不覆盖写了)
	*						↓
	*				a数组:1 2 2 4 5
	*/

	//存储a数组(被排序数组)的最小值:
	int min = a[0]; //默认为首元素

	//存储a数组(被排序数组)的最大值:
	int max = a[0]; //默认为首元素

	//使用for循环找到a数组中的最小值和最大值:
	for (int i = 0; i < n; i++)
	{
		//找最小值:
		if (a[i] < min)
			//当前元素小于min:
		{
			min = a[i]; //将该元素赋给min
		}

		//找最大值:
		if (a[i] > max)
			//当前元素大于max:
		{
			max = a[i]; //将该元素赋给max
		}
	}

	//定义统计数组count的区间(元素个数):
	int range = max - min + 1;
	// [min, max] 左闭右闭,所以元素个数还要+1

	//为统计数组count开辟动态空间:
	int* count = (int*)malloc(sizeof(int) * range);
	//检查是否开辟成功:
	if (count == NULL)
		//开辟后返回空指针:
	{
		//说明开辟失败,打印错误信息:
		perror("malloc fail");
		//返回结束函数:
		return;
	}

	//对统计数组进行初始化:
	memset(count, 0, sizeof(int) * range);
	//使用memset函数将count数组所有元素都初始化为0

	// 1. 使用for循环统计相同元素出现次数:
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
		/*
		* 这里count数组的下标写成:[a[i] - min]
		* 是为了实现 “相对映射” :
		* 被排序数组a可能是:[100, 135, 122, 199, 111] 
		* 这里max为199,传统方式count数组就要开辟 [0, 199]
		* 200个空间,而实际只会使用 [100, 199] 这部分空间,
		* 所以上面 range = 199 - 100 + 1 = 100 只开辟了100个空间,
		* 但是开辟的100个空间,他的下标范围却是 [0, 99]
		* a数组中元素无法对应count数组的下标,所以就需要“相对映射”
		* 
		*					相对映射:
		*	a数组元素 - 节省开辟的空间数(这里是100) = 相对下标
		* 以元素100为例: 100 - 100 = 0 ,
		* 这时 a数组中的元素100 就对应 count数组中下标0,
		* a数组中每有一个元素100,count数组下标0元素++,
		* a数组中其它元素同理。到时进行恢复操作的时候再将
		* count下标 + 节省开辟的空间数(这里是100) 即可
		* (即 a数组中对应元素)
		*/
	}

	// 2. 根据统计的结果将序列回收到原来的序列(数组)中:

	int j = 0; //用于遍历a数组(原数组)下标
	
	for (int i = 0; i < range; i++)
		//range为数组元素所在范围(区间)
	{
		while (count[i]-- != 0)
			/*
			* count[i] 为统计数组的当前下标i元素,
			* 元素为几,说明该元素下标i在a数组中所对应的元素有几个,
			* 如果count数组中i下标元素为0,则不考虑该下标,
			* 这里是将i下标元素不为0的进行操作,
			* 将其i下标在a数组中对应的元素一一恢复:
			*/
		{
			a[j++] = i + min;
			/*
			* 因为我们使用了 "相对映射"
			* 所以 i下标 + 节省开辟的空间数 == a数组中对应元素
			*/
		}
	}
}
//时间复杂度:O(N + range)
/*
* 如果排序的数据比较集中,那么range会比较小 -- O(N)
* 如果不集中,range会比较大 -- O(range)
*/
//空间复杂度:O(range)

            

            

---------------------------------------------------------------------------------------------

             

Test.c -- 排序测试文件

//归并排序(递归版本)测试:
void MSTest()
{
	//创建要进行插入排序的数组:
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };

	//调用快速排序(递归版本)进行排序:
	MergeSort(a, (sizeof(a) / sizeof(int)));

	//使用自定义打印函数打印排序后数组:
	printf("使用归并排序(递归版本)后的数组:> ");
	PrintArray(a, sizeof(a) / sizeof(int));
}


//归并排序(非递归版本)测试:
void MSNRTest()
{
	//创建要进行插入排序的数组:
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };

	//调用快速排序(非递归版本)进行排序:
	MergeSortNonR(a, (sizeof(a) / sizeof(int)));

	//使用自定义打印函数打印排序后数组:
	printf("使用归并排序(非递归版本)后的数组:> ");
	PrintArray(a, sizeof(a) / sizeof(int));
}


//计数排序(鸽巢原理)测试:
void CSTest()
{
	//创建要进行插入排序的数组:
	int a[] = { 100, 135, 122, 199, 111 };

	//调用计数排序(鸽巢原理)进行排序:
	CountSort(a, (sizeof(a) / sizeof(int)));

	//使用自定义打印函数打印排序后数组:
	printf("使用计数排序(鸽巢原理)后的数组:> ");
	PrintArray(a, sizeof(a) / sizeof(int));
}



int main()
{
	//ISTest();
	//SSTest();
	//BSTest();
	//SlSTest();
	//QSTest();
	//QSNRTest();
	//MSTest();
	//MSNRTest();
	CSTest();

	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_63176266/article/details/134111931
今日推荐