排序算法--计数排序

news2024/9/9 4:37:32

计数排序是一种线性时间的排序算法,适用于整数或有限范围内的非负整数排序。它的核心思想是通过计数每个元素的出现次数来进行排序。计数排序不是比较排序,速度快于任何比较排序算法。但对于数据范围很大的数组,需要大量时间和内存。

并且由于目前我们的机器的内存比较充足,为了提高机器的运行效率,就利用了空间换时间的思想。下面,我们详细说说计数排序的内容。


计数排序

算法概念

计数排序区别于冒泡排序和选择排序这两种比较排序,它是非比较排序算法,该算法于1954年由 Harold H. Seward提出,通过计数将时间复杂度降到了O(n)。

算法步骤--基础版

第一步:找出待排序数组arr的最大值max;

第二步:定义一个新的数组cntArr,新数组的长度为max+1,所有元素默认值为0;

第三步:循环遍历待排序数组arr的元素,元素值作为新数组cntArr的下标,新数组的元素就是统计原数组每个元素的个数。

第四步:由于新数组的下标本身就是有序的,循环遍历新数组,如果新数组的某索引值大于0(即代表原数组的有等于该索引值的元素存在),每次循环都需要将cntArr[]的值-1(代表已经排序了,待排序数据少了一个),并将索引值赋值给新开辟的结果数组resArr。依次处理每一个元素。

第五步:返回排序后的数组resArr。

代码实现--基础版

int* cntSort(int* arr, int n) {
	int max = arr[0];
	for (int i = 1; i < n; i++) {
		if (max < arr[i])max = arr[i];
	}
	int* cntArr = new int[max + 1] {0};
	for (int i = 0; i < n; i++) {
		cntArr[arr[i]]++;
	}
	int* resArr = new int[n];
	int j = 0;
	for (int i = 0; i <= max; i++) {
		while (cntArr[i] > 0) {
			resArr[j++] = i;
			cntArr[i]--;
		}
	}delete[] cntArr;
	return resArr;
}

算法分析

假如有一个数组{101,103,105,109,110};明显最大值为110,我们需要开辟大小为111的数组来计数,这明显太过奢侈,前面的[0,100]的空间完全浪费掉了。

按照上述实现的方法有一个这样明显的缺点,就是浪费空间,而且不是一般的浪费。避免是不可能的,算法本身就是空间换时间,但我们怎么能减小这个缺点呢?下面来看一下进阶版的计数排序:

算法步骤--进阶版

1.遍历原数组,找出原数组的最大值max,最小值min;

2.定义计数数组cntArr,大小为max-min+1,所有元素默认为0;

3.遍历原数组,将原数组的元素减去min值作为计数数组的下标,新数组存储该索引值在原数组出现的次数。

4.定义结果数组resArr,大小为原数组大小。

5.遍历统计数组,将数组索引位的值不为零的索引值加上min值存到resArr中。

6.返回resArr

代码实现--进阶版

//计数排序:时间复杂度 O(n),空间复杂度为 O(n),不稳定
int* CountSort(int* arr, int size)
{
	//求最值
	int max = arr[0];
	int min = arr[0];
	for (int i = 1; i < size; i++)
	{
		if (max < arr[i])max = arr[i];
		if (min > arr[i])min = arr[i];
	}

	//开统计数组
	int len = max - min + 1;
	int* cntArr = new int[len]{0};

	//统计
	for (int i = 0; i < size; i++)
		cntArr[arr[i] - min]++;

    //结果数组
    int* resArr=new int[size];
	int index = 0;
	for (int i = 0; i < len; i++)
	{
		while (cntArr[i] > 0)
		{
			resArr[index++] = i + min;
			cntArr[i]--;
		}
	}delete[] cntArr;
    return resArr;
}

算法分析

聪明的小伙伴看到我的第一行注释了,目前来说该算法不够稳定,两个相等的值不能保证他们按照原来的顺序排。当然,这个问题也有解决的办法:

进阶优化

我们想要原始数组中元素值相同的元素在排序后依旧保持相同元素的前后相对位置,该怎么办呢?我们试想一下,不稳定的原因是什么,还不是因为在统计时先统计前位的值,后统计后位的值,在输出存储的时候,先把后位的放出去了,有点意思,像不像栈呢?

那我们想让结果稳定,一个优化方法就是倒序存储:请看VCR(开个玩笑,请各位看代码)。


int* CountSort(int* arr, int size)
{
	//求最值
	int max = arr[0];
	int min = arr[0];
	for (int i = 1; i < size; i++)
	{
		if (max < arr[i])max = arr[i];
		if (min > arr[i])min = arr[i];
	}

	//开统计数组
	int len = max - min + 1;
	int* cntArr = new int[len]{0};


	//统计
	for (int i = 0; i < size; i++)
		cntArr[arr[i] - min]++;

    //结果数组
    int* resArr=new int[size];
	int index = size-1;
	for (int i = len-1; i >=0 ; i--)
	{
		while (cntArr[i] > 0)
		{
			resArr[index--] = i + min;
			cntArr[i]--;
		}
	}delete[] cntArr;
    return resArr;
}

进阶延伸

除了改变存储顺序外,还有办法解决上述不稳定的问题吗?

Of Casue!当然有了。

第一步:找出数组最大值max,最小值min;

第二步:创建计数数组cntArr,长度为max-min+1+1;

第三步:遍历数组元素,以原数组的值作为cntArr数组的索引,以原数组元素的个数作为cntArr的元素值

第四步:对cntArr数组变形,新元素的值是前面元素累加之和,即cntArr[i+1]=cntArr[i+1]+cntArr[i];

第五步:创建结果数组resArr,长度为原数组长度;

第六步:从前向后遍历原始数组的元素,当前元素arr[i]减去最小值min,作为索引,在技术数组中找到对应的元素值cntArr[arr[i]-min]就是arr[i]在结果数组resArr中的位置,做完上述操作,cntArr[arr[i]-min]++;


int* CountSort(int* arr, int size)
{
	//求最值
	int max = arr[0];
	int min = arr[0];
	for (int i = 1; i < size; i++)
	{
		if (max < arr[i])max = arr[i];
		if (min > arr[i])min = arr[i];
	}

	//开统计数组
	int len = max - min + 1;
	int* cntArr = new int[len+1]{0};

	//统计
	for (int i = 0; i < size; i++)
		cntArr[arr[i] - min]++;
    //Add:计数数组变形
    for(int i=1;i<size;i++)
        cntArr[i]+=cntArr[i-1];

    //结果数组
    int* resArr=new int[size];
	for (int i = 0; i < size; i++)
	{
        // 如果后面遇到相同的元素,在前面元素的基础上往后排
        // 如此就保证了原始数组中相同元素的原始排序
        int val=arr[i]-min;//值
        int pos=cntArr[val];//位置
		
        resArr[pos]=arr[i];//赋值
        cntArr[val]++;//自增
	}delete[] cntArr;
    return resArr;
}

最后这块儿挺难理解的,值(arr[i]-min)作为变形计数数组的索引,从该索引处取值,当作arr[i]在结果数组的位置。


感谢大家!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1961232.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机毕设论文】基于SpringBoot的诗词管理系统

&#x1f497;博主介绍&#xff1a;✌全平台粉丝5W,高级大厂开发程序员&#x1f603;&#xff0c;博客之星、掘金/知乎/华为云/阿里云等平台优质作者。 【源码获取】小伙伴可以关注我 感兴趣的可以先收藏起来&#xff0c;同学门有不懂的毕设选题&#xff0c;项目以及论文编写等…

轻松入门Linux—CentOS,直接拿捏 —/— <1>

一、什么是Linux Linux是一个开源的操作系统&#xff0c;目前是市面上占有率极高的服务器操作系统&#xff0c;目前其分支有很多。是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CPU 的操作系统 Linux能运行主要的UNIX工具软件、应用程序和网络协议 Linux支持 32…

小程序开发_05协同工作和发布

一、开发流程 二、权限管理 三、不同成员的权限 四、小程序发布上线的步骤 上传代码--->提交审核-->发布1. 上传代码 提交审核 三、发布上线

文件IO相关作业

1> 使用文件IO完成&#xff0c;将源文件中的所有内容进行加密&#xff08;大写转小写、小写转大写&#xff09;后写入目标文件中 源文件内容不变 #include<myhead.h>int main(int argc, const char *argv[]) {//判断传入的是否是两个文件if(argc!3){write(2,"inp…

期末复习资料——计算机系统基础

第一章 1、下列关于机器字长、指令字长和存储字长的说法中&#xff0c;正确的时_②、③_ ①三者在数值上总是相等的。②三者在数值上可能不相等。③存储字长是存放在一个存储单元中的二进制代码位数。④数据字长就是MDR的位数。 机器字长、指令字长和存储字长&#xff0c;三…

【区块链+绿色低碳】碳低链 | FISCO BCOS应用案例

在碳中和、碳达峰国家战略的号召下&#xff0c;碳中和数字化、协同低碳的发展如火如荼。但是在金融业的实际场景应用中&#xff0c; 存在数据收集效率低、数据核查困难、服务单一等问题&#xff0c;痛点集中为两个&#xff1a;一是数据冗杂&#xff0c;可能会存在数据篡改&…

【python报错已解决】`AttributeError: ‘DataFrame‘ object has no attribute ‘ix‘`

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言&#xff1a; 在数据分析或者数据处理的过程中&#xff0c;我们经常会遇到各种各样的报错信息&#xff0c;这些报错信息往…

【漏洞复现】APP分发签名系统index-uplog.php存在任意文件上传漏洞

漏洞描述 APP分发签名系统index-uplog.php存在任意文件上传漏洞 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事危害国家安全、荣誉和利益,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵…

游戏类App出海广告变现,新手开发者如何选对聚合广告平台?

无论是轻量化小游戏还是中重度游戏&#xff0c;厂商的终极目标都是盈利。从商业化角度来说&#xff0c;单一的变现模式存在一些问题。纯内购驱动的游戏除了少数大竞技品类&#xff0c;大多是为垂直玩家设计&#xff0c;难以扩量。而纯广告变现驱动的游戏&#xff0c;在抗风险方…

所谓有趣的灵魂,实际上就是这个人的信息密度和知识层面,都远高于你!

1. 信息界的“百科全书” 信息密度&#xff1a;大脑里的“硬盘” 我们先来八一八什么是信息密度。想象一下&#xff0c;如果大脑是个硬盘&#xff0c;那么信息密度高的人&#xff0c;硬盘里存的可都是高清大片和无损音乐&#xff0c;随时准备给你来一场视听盛宴。 知识层面&am…

vue3数据结构的渲染01

处理数据&#xff1a; //现有原始数据showCertificateUrl “url01;url02” 使用以下代码将两条通过分号";"分割的url进行处理 const parseUrls () > {urls.value [];// 每次重新赋值前一定要清空之前的旧数据&#xff01;if (!showCertificateUrl.value) {retu…

[前端]解决Iframe即使设置高度100%,但还是显示滚动条scrollbar的问题

前言 好烦,你看看这两个重复的滚动条. 一个是来自iframe,另一个来自父级的div(overflow: auto;) 我已经在css中设置了iframe的height: 100%;border: none;,但无论如何还是显示出了父级的scrollbar 解决 将iframe的display: block;即可. 或者vertical-align: bottom;

安全测试与渗透测试的区别

在这个数字化时代&#xff0c;网络安全如同数字世界的守护神&#xff0c;其重要性不言而喻。而在这场没有硝烟的战争中&#xff0c;安全测试与渗透测试作为两大核心利器&#xff0c;常常被提及却又容易混淆。今天&#xff0c;就让我们揭开它们的神秘面纱&#xff0c;一探二者之…

2024.7.30 作业

1> 使用文件IO完成&#xff0c;将源文件中的所有内容进行加密&#xff08;大写转小写、小写转大写&#xff09;后写入目标文件中&#xff0c;源文件内容不变 #include <myhead.h> int main(int argc,const char *argv[]) {int fd1 -1,fd2 -1;if((fd1 open(".…

【和相同的二元子数组】python刷题记录

R2-前缀和专题 目录 前缀和哈希表 双指针 ps: 第一眼过去&#xff0c;这题应该能用双指针解出来&#xff0c;应该也能用前缀和解题。 前缀和哈希表 适用于 nums[i] 值不固定为 0 和 1 的其他情况 class Solution:def numSubarraysWithSum(self, nums: List[int], goal: i…

Vue入门记录(一)

效果 本文为实现如下前端效果的学习实践记录&#xff1a; 实践 入门的最佳实践我觉得是先去看官网&#xff0c;官网一般都会有快速入门指引。 根据官网的快速上手文档&#xff0c;构建一个新的Vue3TypeScript&#xff0c;查看新建的项目结构&#xff1a; 现在先重点关注comp…

前端科举八股文-手撕代码篇

前端科举八股文-手撕代码篇 手撕排序算法选择排序思路 冒泡排序思路 快速排序思路 手撕bind方法思路解析 手撕js的继承方案构造继承实现原理 原型继承实现原理 组合继承实现原理 寄生组合继承实现原理 手撕instanceOf实现原理 手撕new 操作符手撕防抖实现原理 手撕节流实现原理…

Java8 新特性,看这篇文章就够了

Java8 是 Java 编程语言的一个重要版本&#xff0c;于 2014 年 3 月发布。它引入了许多新的功能和改进&#xff0c;其中包括 Lambda 表达式、Stream API、新的日期/时间 API 和 Nashorn JavaScript 引擎等。 Java 8 的主要特点如下&#xff1a; Lambda 表达式&#xff1a;Lamb…

Linux进程--exec族函数

exec族函数函数的作用&#xff1a; 我们用fork函数创建新进程后&#xff0c;经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时&#xff0c;该进程被完全替换为新程序。因为调用exec函数并不创建新进程&#xff0c;所以前后进程的ID并没有改变。 原文链…

电测量数据交换DLMSCOSEM组件第53部分:DLMSCOSEM应用层(下)

3.DLMS/COSEM应用层协议规范 3.1控制功能 3.1.1客户机侧功能的状态定义 图37显示了客户机侧CF的状态机。 客户机CF(和AL包含CF)的状态定义如下: ——INACTIVE:在该状态下,CF完全没有活动;它既不给AP提供服务,也不使用协议支撑层服务; ——IDLE:在没有AA存在…