计数排序与基数排序

计数排序与基数排序

计数排序

计数排序:使用一个数组记录序列中每一个数字出现的次数,将该数组的下标作为实际数据,元素的值作为数据出现的次数。例如对于序列[3,0,1,1,3,3,0,2],统计的结果为:

0出现的次数:2次
1出现的次数:2次
2出现的次数:1次
3出现的次数:3次

依据出现的次数就可以构建出排序之后的序列

void CountSort(vector<int>& nums) {int minNum = nums.front();int maxNum = nums.front();for (int num : nums) {minNum = std::min(minNum, num);maxNum = std::max(maxNum, num);}int helpSize = maxNum - minNum + 1;int* help = new int[helpSize] {0};//例如最大值为10,最小值为5,需要开辟的数组大小是10-5+1for (int num : nums) {help[num - minNum]++;//统计次数}nums.clear();for (int i = 0; i < helpSize; i++) {while (help[i]--) {nums.push_back(i + minNum);}}delete[] help;
}

计数排序的时间复杂度和空间复杂度

时间复杂度:O(N+max-min),其中max为数组中的最大元素,min为数组中的最小元素。计数排序需要遍历原数组一趟,得到原数组的最大值和最小值从而决定需要开辟的辅助数组的大小,还需要遍历辅助数组得到有序序列

空间复杂度:O(max-min),取决于数组中最大值和最小值的差

计数排序适用于数据量很大,但是数据分布比较密集的场景,例如有1亿个数,它们都在[20,1000]范围,此时就可以使用计数排序,与其它排序算法相比(例如快排、冒泡),计数排序是一种非比较排序

基数排序

在计数排序中,对于数据较为分散的场景,所需开辟的额外空间较大,例如序列[1,56,26,9999,7],若使用计数排序,需要额外开辟一个大小为9999的数组,但是原数组只有5个数需要进行排序,在这种情况下,可以考虑使用基数排序

基数排序也是一种非比较排序,基本思想是:先将原序列按照个位进行排序,在按照十位进行排序……依次类推,直到序列完全有序,以[1,56,26,9999,7]为例,流程如下:

在这里插入图片描述

基数排序的轮数取决于序列中最大值的位数,在进行基数排序时,位数小的数字一定在位数大的数字的前面,例如上图中7虽然在进行第一轮排序完成后处于56的后面,但是当完成第二轮排序后7处于56的前面,因为7的十位为0

如何提取一个数字的个位、十位、百位?

方案一:使用to_string将该数字转化为字符串,依次进行提取

方案二:定义一个offset变量,初始为1,将(num/offset)%10,得到个位,每进行一轮,将offset*10

int num = 3675;
int offset = 1;
int bitNum;
while (bitNum=num / offset) {cout << bitNum % 10 << ' ';offset *= 10;
}
cout << endl;

基数排序的桶

在进行基数排序时一般使用队列作为基数排序的桶,对10进制数字进行排序就需要准备9个队列

void RadixSortByQueue(vector<int>& nums, int bits, int BASE = 10) {//使用队列作为桶进行基数排序//bits表示序列中的最大值的位数//BASE表示多少进制vector<queue<int>> Queues;Queues.resize(BASE);int offset = 1;for (int offset = 1; bits > 0; bits--, offset *= BASE) {//例如最大值为156,则进行3轮for (int num : nums) {int bitNum = (num / offset) % BASE;//得到个位/十位/百位……的数Queues[bitNum].push(num);}//按照个位/十位/百位……排好的数已经放入Queues中nums.clear();for (auto& Queue : Queues) {while (!Queue.empty()) {nums.push_back(Queue.front());Queue.pop();}}}
}

基数排序的优化

前缀数量分区:以序列[1,56,26,9999,7]为例,个位数分别是1,6,6,9,7,可以得到的前缀信息如下

个位数<=1的数据数量:1
个位数<=6的数据数量:3
个位数<=7的数据数量:4
个位数<=9的数据数量:5

那么在每一轮按照特定位进行排序时就不需要使用队列,直接开一个和原始数组等规模的辅助数组help即可,将[1,56,26,9999,7]按照个位进行排序,对于数字7,个位为7,由于个位数<=7的数据数量是4,所以7直接放到help数组中下标为3的位置,此时原序列中的7已经放到help数组中,原序列中个位数<=7的数据数量减少一个,变为3,以此类推,直到原序列中的数据全部按照规则转移到help数组中,此时help数组中的数据就是按照位排序好的数据

void RadixSort(vector<int>& nums, int bits, int BASE = 10) {//bits表示序列中的最大值的位数//BASE表示多少进制int* counts = new int[BASE] {0};int* help = new int[nums.size()]{ 0 };int offset = 1;for (int offset = 1; bits > 0; bits--, offset *= BASE) {memset(counts, 0, sizeof(int) * BASE);for (int num : nums) {int bitNum = (num / offset) % BASE;counts[bitNum]++;//先统计个数}for (int i = 1; i < BASE; i++) {counts[i] += counts[i - 1];//统计前缀数量}for (int i = nums.size() - 1; i >= 0; i--) {int bitNum = (nums[i] / offset) % BASE;help[--counts[bitNum]] = nums[i];}memcpy(nums.data(), help, sizeof(int) * nums.size());}delete[] help;delete[] counts;
}

为什么需要从后往前遍历向help数组中填数据?

以[1,99,7]为例,在排个位数时,可以从后往前,也可以从前往后,没有影响,个位数排完之后,得到[1,7,99],但是在排十位数时,必须从后往前,否则就会打乱1和7的顺序,因为1和7的十位都是0,十位<=0的数的个数是2,7是最后一个十位<=的数,因此在遍历时需要从后往前遍历排到靠后位置.

基数排序的拓展

如果原序列中存在负数,如何进行基数排序?

将原序列中的所有数字加上最小值的绝对值,在进行基数排序,将排完序的结果在减去原来最小值的绝对值。如果存在溢出问题,需要考虑使用long long类型。

void RadixSortContainMinus(vector<int>& nums, int BASE = 10) {int minNum = nums[0];for (int num : nums) {minNum = std::min(minNum, num);}int maxNum = 0;for (int& num : nums) {num -= minNum;maxNum = std::max(maxNum, num);}int bits = 1;while (maxNum / BASE) {bits++;maxNum /= BASE;}RadixSort(nums, bits, BASE);for (int& num : nums) {num += minNum;}
}

如果需要排序的数字不是十进制,如何使用基数排序实现?

若需要排序的数字不是10进制,只需要修改BASE即可,其它思路一致,例如需要排序的数字是16进制,那么counts数组的大小定为16即可,统计每一位在0~f的数量,依然使用前缀分区技巧

基数排序的时间复杂度

基数排序的时间复杂度为O(m*n),其中m表示原序列中最大值的位数,n表示数据量,因为要根据位数确定排多少轮。基数排序的空间复杂度为O(m+n),需要使用一个help数组和一个counts数组,其中counts数组用于统计个数,help数组用于进行保存这一轮排序完毕的数据.

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

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

相关文章

Go面试题

一、Go 1、nil是否一定相等 package mainimport ("fmt" )func main(){var x *int nilvar y interface{} xfmt.Println(xy) //truefmt.Println(xnil) //truefmt.Println(ynil) //false } 2、Go会不会回收线程 package mainimport ("fmt"&quo…

XMLHttpRequest介绍

目录 一、介绍1.创建 XMLHttpRequest2.初始化3.发送请求4.获取响应5.响应类型 二、发送GET请求示例三、发送POST请求示例四、发送POST请求下载文件示例五、发送POST请求上传文件示例 一、介绍 1.创建 XMLHttpRequest let xhr new XMLHttpRequest();2.初始化 xhr.open(metho…

Redis模块二:缓存分类 + Redis模块三:常见缓存(应用)

缓存大致可以分为两大类&#xff1a;1&#xff09;本地缓存 2&#xff09;分布式缓存 目录 本地缓存 分布式缓存 常见缓存的使用 本地缓存&#xff1a;Spring Cache 分布式缓存&#xff1a;Redis 本地缓存 本地缓存也叫单机缓存&#xff0c;也就是说可以应⽤在单机环…

MySQL(2) Explain

1、概念 使用EXPLAIN关键字可以模拟优化器执行SQL语句&#xff0c;分析你的查询语句或是结构的性能瓶颈 2、使用 在 select 语句之前增加 explain 关键字 &#xff0c;MySQL 会在查询上设置一个标记&#xff0c;执行查询会返回执行计划的信息&#xff0c;而不是执行这条SQL 在…

ffmpeg编译 Error: operand type mismatch for `shr‘

错误如下&#xff1a; D:\msys2\tmp\ccUxvBjQ.s: Assembler messages: D:\msys2\tmp\ccUxvBjQ.s:345: Error: operand type mismatch for shr D:\msys2\tmp\ccUxvBjQ.s:410: Error: operand type mismatch for shr D:\msys2\tmp\ccUxvBjQ.s:470: Error: operand type mismatch…

[JAVAee]Spring项目的创建与基本使用

目录 Spring项目的创建 Spring中Bean对象的存储与获取 存储Bean对象 获取并使用Bean对象 getBean方法的重载 本文章介绍了Spring项目创建与使用的过程与一定的注意事项. Spring项目的创建 首先在IDEA中,新建一个Maven 第二步,在pom.xml中写入spring的依赖. pom.xml是mav…

情侣飞行棋 情侣小游戏 2023 抖音

飞行棋网站地址:https://effect.guoyaxue.top/fxq/index.html#/ 以及各种新版来袭&#xff1a; 以及各种情侣小游戏合集 https://fxnew.guoyaxue.top/#/

hive 创建 s3 外表

背景 有个比较大的技术侧需求: 将数据从 HDFS 迁移到 s3。当然在真正迁移之前&#xff0c;还需要验证迁移到 s3 的数据&#xff0c;和上层查询器&#xff08;hive、presto 之间的兼容性&#xff09; 这里我们对一张业务表的数据做个简单的迁移测试 验证 数据迁移 为了让 h…

【微信小程序】swiper的使用

1.swiper的基本使用 <jxz-header></jxz-header> <view class"banner"><swiperprevious-margin"30rpx"autoplayinterval"2000"indicator-dotsindicator-color"rgba(0,0,0,0.3)"indicator-active-color"#bda…

文心一言:如何根据关键词找到想用的Python函数

在Python中&#xff0c;你可以通过以下几种方式来根据关键词找到想用的函数&#xff1a; 使用内置函数dir()和help()&#xff1a; dir()函数可以列出一个模块或对象中可用的属性和方法。help()函数则可以提供有关Python的任何函数、类、模块或关键字的详细信息。 例如&#…

一百七十九、Linux——Linux报错No package epel-release available

一、目的 在Linux中配置Xmanager服务时&#xff0c;执行脚本时Linux报错No package epel-release available 二、解决措施 &#xff08;一&#xff09;第一步&#xff0c;# wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm &#xff08;二&…

uniapp项目实践总结(十七)实现滚动触底加载

导语&#xff1a;在日测的开发过程中&#xff0c;经常会碰到页面需要渲染大量数据的情况&#xff0c;这时候就需要用到滚动加载功能&#xff0c;下面总结一下方法。 目录 原理分析实战演练案例展示 原理分析 使用scrolltolower事件来监听滚动到底部&#xff0c;然后加载下一…

idea之maven的安装与配置

我们到maven的官网里下载maven&#xff0c;地址&#xff1a;https://maven.apache.org/download.cgi下载完成后解压即可配置环境变量 此电脑–>右键–>属性–>高级系统设置–>环境变量–>系统变量&#xff08;S&#xff09;–>新建一个系统变量 变量名&…

第一百四十七回 自定义组件一

文章目录 概念介绍实现方法示例代码 我们在上一章回中介绍了跟手指移动的小球相关的内容&#xff0c;本章回中将介绍 自定义组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 在项目中有些内容无法通过现有的组件来实现&#xff0c;因此需要自定义组件&#xf…

HashMap:hashCode()和equals()方法的正确实习

确保自定义对象作为键时正确实现hashCode()和equals()方法是非常重要的&#xff0c;因为这两个方法直接影响着对象在哈希表&#xff08;如HashMap&#xff09;中的存储和检索。 下面进一步解释为什么这两个方法如此重要&#xff1a; hashCode()方法&#xff1a;这个方法返回一…

【计算机网络】 粘包问题

文章目录 为什么会产生粘包问题&#xff1f;解决办法先发包大小再发包内容代码示例 为什么会产生粘包问题&#xff1f; tcp是数据流传输&#xff0c;是一种没有边界的&#xff0c;可以合并的传输数据方式。合并就要能拆开&#xff0c;拆不开就是粘包。 解决办法 设置标志位&a…

共享股东模式:实体门店的新型股东模式机遇与挑战并存

在传统的经营模式中&#xff0c;经营者通常需要独自承担企业的风险和责任&#xff0c;同时也需要投入大量的人力和资金。对很多创业者而言&#xff0c;这是一项巨大的挑战。有没有一种方法能够使创业者们在共享资源和利益的同时分担风险和责任呢&#xff1f; 共享股东模式是一…

Spring MongoDB

首先创建数据实体&#xff0c;Document(collection "name") 建立一个一对多的关系时&#xff0c; DocumentReference 导致数据库仅仅存储条目的Id&#xff0c;称为手动索引关系。 也有其它的方法在MongoDB中创建关系。 接下来创建Controller。 RestController …

短效代理IP与长效代理IP:应用场景与选择方法

随着互联网的迅速发展&#xff0c;越来越多的网站开始采取反爬虫措施&#xff0c;以保护自己的数据安全。而作为一种常见的反爬虫工具&#xff0c;代理IP也被广泛应用。代理IP分为短效代理IP和长效代理IP两种类型&#xff0c;本文将介绍它们的区别和应用场景。 一、短效代理IP …

数据结构-----队列

目录 前言 队列 定义 队列的定义和操作方法 队列节点的定义 操作方式 顺序表实现队列&#xff08;C/C代码&#xff09; 链表实现队列&#xff08;C/C代码&#xff09; Python语言实现队列 前言 排队是我们日常生活中必不可少的一件事&#xff0c;去饭堂打饭的时候排队&a…