当前位置: 首页 > news >正文

归并排序/计数排序

1:归并排序

1.1:代码

void _MergeSort(int* arr, int left, int right, int* tmp)
{if (left >= right){return;}int mid = (left + right) / 2;						_MergeSort(arr, left, mid, tmp);					_MergeSort(arr, mid+1, right, tmp);					int begin1 = left;									int end1 = mid;										int begin2 = mid + 1;								int end2 = right;									int i = begin1;										while (begin1 <= end1 && begin2 <= end2)			{													if(arr[begin1] < arr[begin2])					{												tmp[i++] = arr[begin1++];					}												if (arr[begin1] > arr[begin2])					{												tmp[i++] = arr[begin2++];					}												}													while (begin1 <= end1)								{													tmp[i++] = arr[begin1++];						}													while (begin2 <= end2)								{													tmp[i++] = arr[begin2++];						}													for (int i = left; i <= right; i++)					{													arr[i] = tmp[i];								}										    
}void MergeSort(int* arr, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);	_MergeSort(arr, 0, n - 1, tmp);
}

1.2:递归过程

图一:

	if (left >= right){return;}int mid = (left + right) / 2;						_MergeSort(arr, left, mid, tmp);					_MergeSort(arr, mid+1, right, tmp);		

通过这三行代码,可以得到如图所示的结果,当传递的 left 和 right 满足 if 条件时,递归开始返回,

图二:

注:方框内容为自定义函数:void _MergeSort(int* arr, int left, int right, int* tmp)

注:对于新手(编者我而言)需要知道的是,每次递归,都会向系统开辟新的空间,而原先的空间会临时贮存在空间中,通过return返回重新调用该函数。

第一次返回时,来到D这个位置,接下来就将执行排序,即如下代码:该函数中 left = 0  mid = 0 right = 1,是对两个元素进行排列。

int begin1 = left;						
int end1 = mid;							
int begin2 = mid + 1;					
int end2 = right;						int i = begin1;							
while (begin1 <= end1 && begin2 <= end2)
{										if(arr[begin1] < arr[begin2])		{									tmp[i++] = arr[begin1++];		}									if (arr[begin1] > arr[begin2])		{									tmp[i++] = arr[begin2++];		}									
}										while (begin1 <= end1)					
{										tmp[i++] = arr[begin1++];			
}										
while (begin2 <= end2)					
{										tmp[i++] = arr[begin2++];			
}										

当前范围内的数组排列完毕时,需要将临时数组的值传递给原数组,以便于下一次继续比较排序,

代码如下:

for (int i = left; i <= right; i++)	
{									arr[i] = tmp[i];				
}									

left 和 right 分别对应数组左右临界,而left ~ right 中间的范围正是需要赋值的对象。

当D排序完毕后,系统会将其释放,此时来到B处,开始对右边数组开始递归,直至return,与左边递归类似,如图三所示:

图三:

当D中和E中的数组全部排列完毕,并且赋值给原数组时,此时会返回到B,对B中数组元素进行排列,,最后我们能够得到如下图所示的结果:

图四:

到B中函数排列完毕,并将临时数组中的值赋值给arr时,此时系统会将其空间释放,并返回到A中,开始递归右边部分函数,其过程如下图所示。

图五:

而对于右半部分的递归与左半部分相似,对于新人而言(我)注意其左临界值left的变化即可,其次通过上述过程不难发现,归并排序其实是一个后序遍历二叉树结点的过程,通过对左右子节点中的数组元素进行排列,最终在根节点处再次排列得到有序数组。

1.3:归并排序特性总结

时间复杂度:O(nlogn)

空间复杂度:O(n) —— 至于为什么是 n 而不是 logn 是因为以malloc 开辟了一块 n 大小的内存空间

2:计数排序

2.1:代码

void CountSort(int* arr, int n)		
{									int max, min;max = min = arr[0];for (int i = 0; i < n; i++){if (arr[i] > max){max = arr[i];}if (arr[i] < min){min = arr[i];}}int range = max - min + 1;int* tmp = (int*)malloc(sizeof(int) * range);assert(tmp);memset(tmp, 0, sizeof(int) * range);for (int i = 0; i < n; i++){tmp[arr[i] - min]++;}int index = 0;for (int i = 0; i < range; i++){while (tmp[i]--){arr[index++] = i + min;}}							
}			

2.2:思路

计数排序的思想:先找到数组中的最大值和最小值,定义 range (range = max - min +1) 个大小空间的数组 ,为了是大数据能够更好的存放进数组,同时又不会浪费太多空间。

我们需要把原数组的数据 - min 存放进新数组相应位置处,原数组中每重复一个数字,新数组相应位置处的大小就+1,这就意味着计数排序要求原数组中,各个数据相差不是很大,否则会造成空间的浪费。

计数排序的巧妙在于,我们不用去比较各个元素然后排序,而是统计原数组中,每个元素出现的次数,并将该元素 - min (这个差对应新数组下标)存入到数组中,如果两个值相差不大 ,得到的差会对应于新数组的一个下标,即新数组中,统计的是该元素在原数组中出现的次数,最后我们再进行排序时,以新数组元素个数为条件出发,同时新数组下标+min 又可以得到原数组中的元素,从而实现排列。

2.3:代码分析

下列代码我们开辟了一块 range 大小的空间区域

	int max, min;max = min = arr[0];for (int i = 0; i < n; i++){if (arr[i] > max){max = arr[i];}if (arr[i] < min){min = arr[i];}}int range = max - min + 1;int* tmp = (int*)malloc(sizeof(int) * range);assert(tmp);memset(tmp, 0, sizeof(int) * range);

下列代码统计了原数组中,每个元素出现的次数,并存入到临时数组中

for (int i = 0; i < n; i++)
{tmp[arr[i] - min]++;
}

最后对临时数组进行访问,以临时数组中元素个数为条件,临时数组下标地址 + min 得到原数组的值:

int index = 0;
for (int i = 0; i < range; i++)
{while (tmp[i]--){arr[index++] = i + min;}
}		

2.4:图示上述过程:

初始时如图所示:

当进入第二个for循环时,开始统计原数组中的元素个数,当循环结束时,得到如下图所示变量关系:

当进入第三个for循环时,遍历tmp中各个元素,同时内层以各个元素的值作为条件,循环的将 i + min (原数组对应值) 赋值给原数组中,当外层 for 循环完成对数组的遍历时,此时原数组也得到了正确的顺序。

2.5:计数排序的特性

适用范围:数据范围比较集中时,效率很高

时间复杂度:O(N+range)

空间复杂度:O(range)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Spring Boot之数据访问集成入门
  • 秋招想要过在线测评,这些知识必须刷
  • [SUCTF 2018]annonymous1
  • FFmpeg源码:avcodec_descriptor_get函数分析
  • 三维重建实战:3D Gaussian Splatting
  • 数学建模强化宝典(14)Fisher 最优分割法
  • 鲁棒优化 形象讲解 和库存管理鲁棒优化、生产线调度鲁棒优化、电力市场鲁棒优化、 物流优化鲁棒优化
  • 每日一题,力扣leetcode Hot100之238.除自身以外数组的乘积
  • 小散想在a股量化交易,怎么解决下单api问题
  • golang panic
  • 828华为云征文|部署RedisStack+可视化操作
  • springboot websocket 服务端
  • 计算机毕业设计Spark+PyTorch知识图谱房源推荐系统 房价预测系统 房源数据分析 房源可视化 房源大数据大屏 大数据毕业设计 机器学习
  • 借助ChatGPT高效撰写优质论文的7大要素
  • 使用SQL语句查询MySQL数据表
  • [deviceone开发]-do_Webview的基本示例
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • canvas 五子棋游戏
  • Docker入门(二) - Dockerfile
  • EventListener原理
  • FineReport中如何实现自动滚屏效果
  • Fundebug计费标准解释:事件数是如何定义的?
  • JS实现简单的MVC模式开发小游戏
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • PHP 7 修改了什么呢 -- 2
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • SpiderData 2019年2月16日 DApp数据排行榜
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • 规范化安全开发 KOA 手脚架
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 最简单的无缝轮播
  • 2017年360最后一道编程题
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • # .NET Framework中使用命名管道进行进程间通信
  • #laravel部署安装报错loadFactoriesFrom是undefined method #
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #pragma data_seg 共享数据区(转)
  • #数据结构 笔记一
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • (HAL库版)freeRTOS移植STMF103
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (力扣)1314.矩阵区域和
  • (力扣题库)跳跃游戏II(c++)
  • (三)终结任务
  • (学习总结16)C++模版2
  • **《Linux/Unix系统编程手册》读书笔记24章**
  • *算法训练(leetcode)第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .net core使用RPC方式进行高效的HTTP服务访问
  • .NET MAUI Sqlite程序应用-数据库配置(一)
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题