C++不知算法系列之计数排序算法的计数之巧

news2024/11/20 12:41:15

1. 前言

计数排序是较简单的排序算法,其基本思想是利用数组索引号有序的原理。

如对如下的原始数组中的数据(元素)排序:

//原始数组
int nums[5]={9,1,7,6,8};

使用计数排序的基本思路如下:

  • 创建一个排序数组。数组的大小由原始数组的最大值决定,如原始数组的最大值为9,则排序数组的长度为 9+1。为什么排序数组的长度需要如此设置,后文将做解释。
int sortNums[10]={0}; //初始化值为 0
  • 读取原始数组中的数据,以此数据作为排序数组索引号,此数据出现的次数为排序数组的值。

    这也解释了为什么排序数组的长度必须是原始数组中最大值加1。因为排序数组必须能为原始数组中的最大值提供索引号。

1.png

  • 然后输出排序数组中的值不为 0的索引号。

2.png

编码实现:

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
	//原数组
	int nums[5]= {9,1,7,6,8};
	//排序数组
	int sort[10]= {0};
	//转存
	for(int  i=0; i<5; i++) {
		sort[nums[i]]++;
	}
	//输出排序数组
	for(int i=0; i<10; i++) {
		if(sort[i]!=0)
			cout<<i<<"\t";
	}
	return 0;
}

输出结果:

3.png

通过上文简述可知:

  • 计数排序的时间复杂度为O(n),时间复杂度还算可观。
  • 但是空间复杂度也是O(n)。相比较如冒泡、选择……排序算法,计数排序算法是以空间换取时间。

2. 两个问题

2.1 排序数组的长度

计数排序利用数组索引号的有序而对数据排序,所以,需要把原无序数组中的数据映射到排序数组的索引号上。于是,对排序数组的长度就会有一个最小值的约束,至少等于无序数组中的最大值加一。

如下面的无序数组:

int num[]={500,420,550};

为了保证无序数组中的数据能映射到对应的索引号,则排序数组长度至少应该为 551

int sort[551]={0};

而实际需要映射的数据只有 3 个,会导致排序数组空间浪费巨大,这也是计数排序缺点所在。

如下图所示:

4.png

如何解决此问题?

可以在创建排序数组时:

  • 找到原始无序数组中的最大值(max)和最小值(min)。如上文无序数组的最大值为 550,最小值为420

  • 指定排序数组的长度为:max-min+1,即排序数组的长度为:131

    int sort[131]={0}; //初始值为0
    
  • 无序数组到排序数组的映射规则:排序数组中的索引号=无序数组中的数据-最小值

    反之在遍历排序数组时:无序数组中的数据=排序数组中的索引号+最小值

5.png

编码实现:

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
	//原数组
	int nums[3]= {500,420,550};
	//硬代码求长度 
	int len=550-420+1; 
	//排序数组
	int sort[len]= {0};
	//转存
	for(int  i=0; i<3; i++) {
		sort[nums[i]-420 ]++;
	}
	//输出排序数组
	for(int i=0; i<len; i++) {
		if(sort[i]!=0)
			cout<<(i+420)<<"\t";
	}
	return 0;
}

输出结果:

6.png

2.2 重复问题

如果无序数组中有重复数据,根据计数排序算法的映射原理,显然,相同数据会映射到排序数组的同一个位置。排序数组通过计数器方案对相同数据进行计数。这也是计数排序算法名称的由来。

如下图所示:无序数组中的 2129映射到了排序数组的同一个位置,排序数组的值记录了重复数据的多少。

7.png

编码实现:

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
	//原数组
	int nums[5]= {9,1,7,1,9};
	//排序数组
	int sort[10]= {0};
	//转存
	for(int  i=0; i<5; i++) {
		sort[nums[i] ]++;
	}
	//输出排序数组
	for(int i=0; i<10; ) {
		if(sort[i]!=0) {
			cout<<i<<"\t";
			sort[i]--;
		}else{
             i++;
        }
	}
	return 0;
}

输出结果:

8.png

此处只能对重复的数据计数,但无法得知重复数据的原始顺序。故,理论而言,计数排序算法是不稳定的。

有没有方案能输出时保留重复数据的原始先后顺序?

答案是:改造排序数组中的值,数组中的映射位置不再存储此索引号对应数据的个数,而是存储此索引号之前所有数据的个数。

9.png

然后逆向遍历原始无序数组。用其值做为排序数组的索引号,找出存储在排序数组中的值然后减一,便知道此数据应该排在有序位置的第几位。

10.png

为什么要逆向遍历?

原因很简单,在映射时,是正向遍历,则无序数组中的第 19一定是先映射到排序数组的索引号为 9的位置,最后的一个 9是后映射到排序数组索引号为 9的位置。拿出来时,应该要遵循先进后出原则。

编码实现:

#include <iostream>
using namespace std;
int main(int argc, char** argv) {
	//原数组
	int nums[5]= {9,1,7,1,9};
	//排序数组
	int sort[10]= {0};
	//映射
	for(int  i=0; i<5; i++) {
		sort[nums[i] ]++;
	}
	//转值,排序数组中存储此索引号及之前已经映射的数据个数
	for(int i=1; i<10; i++) {
		sort[i]+=sort[i-1];
	}
	//结果数组
	int res[5]= {0};
	//逆向遍历原无序数组
	for(int i=4; i>=0; i-- ) {
         //无序数组中的数据作为排序数组的索引号,其值减一,即为 nums[i]的正确位置
		res[--sort[nums[i]]]=nums[i];	
	}
	//输出结果
	for(int i=0;i<5;i++){
		cout<<res[i]<<"\t";
	} 
	return 0;
}

输出结果:

11.png

3. 完整的代码及应用

3.1 完整代码

上文对计数排序的实现流程做了分步讲解,综合基本思想以及其问题解决方案。下面是完整的代码。

#include <iostream>
using namespace std;
/*
*查找数组中的最大值、最小值
*/
pair<int,int> getMaxAndMin(int nums[],int size) {
	int mixn=nums[0];
	int maxn=nums[0];
	for(int i=1; i<size; i++) {
		if(nums[i]>maxn)
			maxn=nums[i];
		if(nums[i]<mixn)
			mixn=nums[i];
	}
	pair<int,int> p(mixn,maxn);
	return p;
}
/*
*计数排序
*/
void jsSort(int nums[],int size,int res[]) {
	pair<int,int> p=getMaxAndMin(nums,size);
	int mx=p.second;
	int mi=p.first;
	int sortLen=mx-mi+1;
	//排序数组
	int sort[ sortLen ]= {0};
	//映射且计数
	for(int i=0; i<size; i++) {
		sort[nums[i]-mi]++;
	}
	//计总数
	for(int i=1; i<sortLen; i++) {
		sort[i]+=sort[i-1];
	}
	//逆向遍历原数组
	int idx=0;
	for(int i=size-1; i>=0; i--) {
		//有序位置 
		idx= --sort[nums[i]-mi];
		res[idx]=nums[i];
	}
}
int main(int argc, char** argv) {
	int nums[5]= {9,1,7,1,9};
	int size=sizeof(nums)/4;
	//结果数组
	int res[size]= {0};
	jsSort(nums,size,res);
	for(int i=0; i<5; i++) {
		cout<<res[i]<<"\t";
	}
	return 0;
}

3.2 应用

2019-10-19CSP-J试卷中有一道与计数排序算法有关的程序题。

题目描述:

(计数排序)计数排序是一个广泛使用的排序方法。下面的程序使用双关键字计数排序,将n10000以内的整数,从小到大排序。
例如有三对整数(3,4)、(2,4)、(3.3),那么排序之后应该是(2,4)、(3,3)、(3,4)
输入第一行为n,接下来n行,第i行有两个数a[i]b[i],分别表示第 i对整数的第一关键字和第二关键字。从小到大排序后输出。
数据范围1≤n≤10^7,1≤a[i],b[i]≤10^4
提示:应先对第二关键字排序,再对第一关键字排序。数组ord[]存储第二关键字排序的结果,数组res[]存储双关键字排序的结果。

试补全程序:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int maxn=10000000;
const int maxs=10000;
int n;
unsigned a[maxn],b[maxn],res[maxn],ord[maxn];
unsigned cnt[maxs+1];
int main() {
	scanf("%d",&n);
	for(int i=0; i<n; ++i) {
		scanf("%d%d",&a[i],&b[i]);
	}
	memset(cnt,0,sizeof(cnt));
	for(int i=0; i<n; ++i)
		1 ; //使用 cnt 数据计数 
	for(int i=0; i<maxs; ++i) 
		cnt[i+1]+=cnt[i];
	for(int i=0; i<n; ++i) 
		 2 ;
	memset(cnt,0,sizeof(cnt));
	for(int i=0; i<n; i++) 
		 3 ;
	for(int i=0; i<maxs; ++i) 
		cnt[i+1]+=cnt[i];
	for(int i=n-1; i>=0; --i)
		  4 ;
	for(int i=0; i<n; ++i)
		printf("%d %d\n",  5 );
	return 0;
}
  1. ①处应填( B )

A、 ++cnt[i]

B、 ++cnt[b[i]]

C、 ++cnt[a[i] * maxs + b[i]]

D、 ++cnt[a[i]]

2) ②处应填( D

A、 ord[--cnt[a[i]]] = i

B、ord[--cnt[b[i]]] = a[i]

C、 ord[--cnt[a[i]]] = b[i]

D、 ord[--cnt[b[i]]] = i

3) ③处应填( C

A. ++cnt[b[i]]

B. ``++cnt[a[i] * maxs + b[i]]`

C. ++cnt[a[i]]

D. ++cnt [i]

4) ④处应填( A

A、 res[--cnt[a[ord[i]]]] = ord[i]

B、 res[--cnt[b[ord[i]]]] = ord[i]

C、 res[--cnt[b[i]]] = ord[i]

D、 res[--cnt[a[i]]] = ord[i]

5) ⑤处应填( B

A、 a[i], b[i]

B、 a[res[i]], b[res[i]]

C、 a[ord[res[i]]] , b[ord[res[i]]]

D、 a[res[ord[i]]] , b[res[ord[i]]]

4. 总结

计数排序、桶排序以及基数排序是类似的排序算法。相比较计数排序时数组纵向长度的不可控,基数排序使用二维数组对数据排序,且把数组的大小限定在的 10X10之间,空间大小可控的。但是,从时间复杂度上讲,计数排序更胜一筹。

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

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

相关文章

ActiveMQ消息队列的核心概念

文章目录 ActiveMQ消息队列的核心概念1.什么是MQ消息队列2.为什么要使用MQ消息队列3.MQ消息队列的应用场景3.1.异步处理3.2.应用解耦3.3.流量削锋 4.常见的MQ消息队列产品对比 ActiveMQ消息队列的核心概念 1.什么是MQ消息队列 Message Queue消息队列简称MQ&#xff0c;消息队…

NXP i.MX 6ULL工业开发板规格书( ARM Cortex-A7,主频792MHz)

1 评估板简介 创龙科技TLIMX6U-EVM是一款基于NXP i.MX 6ULL的ARM Cortex-A7高性能低功耗处理器设计的评估板&#xff0c;由核心板和评估底板组成。核心板经过专业的PCB Layout和高低温测试验证&#xff0c;稳定可靠&#xff0c;可满足各种工业应用环境。 评估板接口资源丰富&…

AI通用大模型 —— Pathways,MoE, etc.

文章目录 Pathways现有AI缺憾Pathways Can DoMultiple TasksMultiple SensesSparse and Efficient Mixture of Experts&#xff08;MoE&#xff09;Neural Computation1991, Adaptive mixtures of local expertsICLR17, Outrageously Large Neural Networks: The Sparsely-Gate…

【已解决】ModuleNotFoundError: No module named ‘timm.models.layers.helpers‘

文章目录 错误信息原因解决方法专栏&#xff1a;神经网络精讲与实战AlexNetVGGNetGoogLeNetInception V2——V4ResNetDenseNet 错误信息 在使用timm库的时候出现了ModuleNotFoundError: No module named timm.models.layers.helpers’的错误&#xff0c;详情如下&#xff1a; …

Windows下安装ClickHouse图文教程

文章目录 1.安装WSL21.1启用适用于 Linux 的 Windows 子系统1.2启用Windows虚拟机功能1.3将WSL2设置为默认版本1.4下载Linux内核更新包1.5安装Linux子系统1.6设置账户和密码 2.安装Docker2.1下载与安装2.2设置镜像地址 3.安装Clickhouse3.1拉取镜像3.2启动clickhouse-server3.3…

Docker学习笔记7

启动一个运行httpd服务的容器&#xff1a; docker run -it --namec3 centos:latest /bin/bash 在容器中安装apache服务&#xff1a; yum install -y httpd 在这个过程中遇到一个问题&#xff1a; Error: Failed to download metadata for repo appstream: Cannot prepare …

关于PHP调用openAI chatGPT一些问题

我是直接使用gpt生成的curl代码区调用的 1&#xff1a;windows可能出现代理访问问题&#xff0c;报443端口问题 解决办法&#xff1a;开启代理后&#xff0c;需要到设置 确认这里打开状态 在curl请求里面加上对应的代码 curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1&qu…

java spring cloud 企业工程项目管理系统源码-全面的工程项目管理

​ ​工程项目管理系统是指从事工程项目管理的企业&#xff08;以下简称工程项目管理企业&#xff09;受业主委托&#xff0c;按照合同约定&#xff0c;代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 如今建筑行业竞争激烈&#xff0c;内卷严重&#xff0c…

企业运维 | NFS、Samba服务在Docker容器环境中快速搭建部署实践

欢迎关注「全栈工程师修炼指南」公众号 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习&#xff01; 专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享 “ 花开堪折直须折&#xff0c;莫待无花空折枝。 ” 作者主页&#xff1…

chatgpt赋能python:Python能自举吗?

Python能自举吗&#xff1f; Python是一门高级编程语言&#xff0c;由Guido van Rossum于1989年开发而来。Python被广泛用于Web开发、数据分析、人工智能等领域。因为Python语法简洁易懂&#xff0c;易于学习&#xff0c;许多人都称其为“入门语言”。 那么问题来了&#xff…

Spring Cloud——演进与应用的分布式系统开发利器

&#x1f338;作者简介&#xff1a;花想云&#xff0c;目前大二在读 &#xff0c;C/C领域新星创作者、运维领域新星创作者、CSDN2023新星计划导师、CSDN内容合伙人、阿里云专家博主、华为云云享专家 &#x1f338;专栏推荐&#xff1a;C语言初阶系列 、C语言进阶系列 、C系列、…

Linux之CentOS 7.9部署Oracle 11g r2_p13390677_112040最终版简易安装实测验证(桌面模式)

前言&#xff1a; Linux之CentOS 7.9部署Oracle 11g r2最终版安装实测验证&#xff08;桌面模式&#xff09; 介于前段时间的Windows以及linux无桌面模式环境&#xff0c;之前的linux oracl源包因缺失会存在报错现象&#xff0c;这次主要以oracle 11gr2更新包来记录下部署方式&…

不挂脖、不入耳,南卡NANK-OE骨传开放式蓝牙耳机

蓝牙耳机几乎成为和手机相同的EDC设备&#xff0c;无论是在工作还是通勤过程&#xff0c;无论是娱乐还是线上办公&#xff0c;随身携带的蓝牙耳机都能提供更舒适、更便捷的听觉和通话体验。 目前主流的蓝牙耳机有头戴式、入耳式和骨传耳机三类&#xff0c;考虑到使用的便携性普…

JavaScript:一门强大的脚本语言,掌握它,开启前端开发之路

目录 一、JavaScript的发展历史背景 二、JavaSxript的组成 三、javascript的基本特点 四、javascript的特性 五、Javascript的使用方式 六、JavaScript与Java的区别 七、JavaScript与JScript的区别 一、JavaScript的发展历史背景 JavaScript的发展历史可以追溯到20世纪9…

4 带你学MATLAB图像处理关键技术(matlab代码)

学习目标&#xff1a;学习图像处理关键技术知识点 %% 获取RGB图像中具体的3个像素点的像素值 clear all; RGBimread(peppers.png); r[12 23 36]; c[35 40 60]; pimpixel(RGB,r,c) %% 获取任意一点的像素值 非常方便 clear all; close all; imshow(peppers.png); himpixelin…

生产模块-多计量单位防坑提示

文章目录 一、概述二、数据案例2.1、物料主数据&#xff1a;2.2、业务数据准备2.3、库存数据查看2.3.1库存数据&#xff1a;基本计量单位&#xff1a;KG库存2.3.2、辅助单位单位&#xff1a;EA库存2.3.3、出库操作&#xff1a;以562方式为例场景1&#xff1a;场景2&#xff1a;…

进阶课程1:jvm内存模型

目录 JVM内存结构转换总结一下 JVM整体结构线程栈 JVM栈内存结构栈帧操作数栈局部变量表 JVM堆内存结构堆内存非堆内存 JMM内容&#xff1a;讲解JMM的三大特性1.原子性2.可见性3.有序性 视频 JVM内存结构 JVM内部由线程栈和堆内存组成。 简单描述就是我们的原生类型的局部变量…

Redis学习基础篇

目录 初识Redis 认识NoSQL 关联的 非关联的​编辑 认识Redis 安装Redis 1.1.安装Redis依赖 1.2.上传安装包并解压 该目录以及默认配置到环境变量&#xff0c;因此可以在任意目录下运行这些命令。其中&#xff1a; 图形化工具下载&#xff1a;Releases lework/RedisDe…

什么是 CSR,SSR,SSG 渲染

在Web开发中&#xff0c;网页渲染是一个重要的过程&#xff0c;它关系到网页的渲染速度和SEO排名&#xff0c;下面列举三种常见的网页渲染方式。 一、什么是浏览器端渲染 (CSR) CSR&#xff08;Client-Side Rendering&#xff09;是指将网页的内容生成和渲染都放在客户端&…

什么是真正的骨传导耳机,介绍几款高性价比的骨传导耳机

随着越来越多的人喜欢户外运动&#xff0c;骨传导耳机也逐渐被人们所熟知。骨传导耳机是通过颅骨和内耳传递声音到听觉中枢&#xff0c;所以听感相对较好&#xff0c;不会对耳朵造成任何损伤&#xff0c;因此在户外运动中使用骨传导耳机是一个很好的选择。接下来我会给大家介绍…