class 028 基数排序

news2024/11/19 3:20:04

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。

这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐.
https://space.bilibili.com/8888480?spm_id_from=333.999.0.0

在这里插入图片描述

1. 不基于比较的排序

1.1 基于比较的排序

就是制定一个排序的标准, 定义一个比较器, 是通用的. 最小最小的时间复杂度也有:O(n * log(n)).

1.2 不给予比较的排序

对于数据特征是有要求的, 但是可以将时间复杂度压到 O(n). 比如计数排序(对范围很敏感).

2. 基数排序

基数排序所需要的数字特征和要求都在上面的图片中说明了.

2.1 逻辑实现

设置一个数组:arr[] = [23, 51, 42, 24, 6]. 这个数字中最大的数字是:51, 这个数字有两个位数:十位和个位, 所以我们要分两次准备桶, 第一次准备一个装个位数字的桶, 第二次准备一个装十位数字的桶.

  1. 先将所有的数字的个位数字设置桶:
    1. 1 的桶:51,
    2. 2 的桶:42,
    3. 3 的桶:23,
    4. 4 的桶:24,
    5. 6 的桶,
    6. 然后按照桶由小到大的顺序将所有的数字倒出:arr[] = [51, 42, 23, 24, 6].
  2. 然后将所有数字的十位数字设置桶:
    1. 0 的桶:6,
    2. 2 的桶:23, 24, (将来倒出的时候:先进去的数字先出去).
    3. 4 的桶:42,
    4. 5 的桶:51.
    5. 然后将桶的顺序由小到大的顺序将所有的数字倒出:arr[] = [6, 23, 24, 42, 51].
  3. 最后排好序:arr[] = [6, 23, 24, 42, 51].

注意:在个位数字排好序之后, 在十位数字排序的时候, 会保留本身的相对次序,
例子:arr[] = [21, 22, 23, 24, 25],

  1. 先进行个位数字:
    1. 1 的桶:21,
    2. 2 的桶:22,
    3. 3 的桶:23,
    4. 4 的桶:24,
    5. 5 的桶:25.
    6. 将所有的数字倒出:arr[] = {21, 22, 23, 24, 25}.
  2. 然后进行十位数字:
    1. 2 的桶:21, 22, 23, 24, 25.(是先进先出的).
    2. 将所有的数字倒出:arr[] = {21, 22, 23, 24, 25}.

2.2 如何设置桶, 优化实现

2.2.1 第一个优化:前缀和

有一个数组:arr[] = {3, 0, 3, 2, 1, 3, 0, 0, 1, 2, 2}. 有 11 个数字, 此时我们希望将所有的数字都放到 help数组 中,
此时我们先统计个位中小于等于自己的有几个数字:0位:3个, 1位:5个, 2位:8个, 3位:11个, 然后我们将 arr数组 中的数字从后往前遍历,

  1. 2 放到 help数组的 7 位置, 因为已经统计过了:2位有 8 个, 所以对应的可以将 2 放到 7 位置, 因为这个 2 是所有数字中最后一个位置的 2, 因为我们想要保持相对次序, 所以对应的, 我们要将 2 放到 2 最后的位置.
  2. 然后将 2位置的数字 - 1, 此时 2位:7个.
  3. 此时来到倒数第二个数字, 还是 2, 此时将其放到 6位置, 因为这个 2 是剩下的所有 2 里最后一个 2, 所以要和原来一样, 放到 6位置, 然后将 2位置的数字 - 1, 此时 2位:6个.
  4. 此时来到倒数第三个数字, 是 1, 此时将其放到 4 位置, 因为这个 1 是剩下的所有 1 里最后一个 1, 所以放到 4位置,
  5. 然后将 1位置的数字 - 1, 此时:1位:4个.
  6. 之后的操作一直和上述一样. 可以是所有的数字都保持相对次序.

这个方法扩展之后:比如:arr[] = {290, 45, 33, 111}, 还是只看个位, 按照上述方式进行排序. 然后按照十位, 还是安装上述方式进行排序.

2.2.2 如何得到每一个数字的位数

比如:提取 17293 这个数字的个位, 十位, 百位, 千位, 万位.

先设置一个变量:offset = 1, 和个位数字对齐, 然后将 (17293 / 3) % 10. 这样就能提取出个位数字:3.
然后将 offset * 10, 此时和十位数字对齐, 然后还是将 (17293 / 3) % 10. 这样就能提取出十位数字:9.
以此类推, 每一次都 将 offset * 10.

3. 基数排序的代码实例

// 可以设置进制,不一定10进制,随你设置  
public static int BASE = 10;  
  
public static int MAXN = 100001;  
  
public static int[] arr = new int[MAXN];  
  
public static int[] help = new int[MAXN];  
  
public static int[] cnts = new int[BASE];  
  
public static int n;  
  
public static void main(String[] args) throws IOException {  
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
    StreamTokenizer in = new StreamTokenizer(br);  
    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));  
    in.nextToken();  
    n = (int) in.nval;  
    for (int i = 0; i < n; i++) {  
       in.nextToken();  
       arr[i] = (int) in.nval;  
    }  
    sort();  
    for (int i = 0; i < n - 1; i++) {  
       out.print(arr[i] + " ");  
    }  
    out.println(arr[n - 1]);  
    out.flush();  
    out.close();  
    br.close();  
}  
  
public static void sort() {  
    // 如果会溢出,那么要改用long类型数组来排序  
    // 找到数组中的最小值  
    int min = arr[0];  
    for (int i = 1; i < n; i++) {  
       min = Math.min(min, arr[i]);  
    }  
    int max = 0;  
    for (int i = 0; i < n; i++) {  
       // 数组中的每个数字,减去数组中的最小值,就把arr转成了非负数组  
       arr[i] -= min;  
       // 记录数组中的最大值  
       max = Math.max(max, arr[i]);  
    }  
    // 根据最大值在BASE进制下的位数,决定基数排序做多少轮  
    radixSort(bits(max));  
    // 数组中所有数都减去了最小值,所以最后不要忘了还原  
    for (int i = 0; i < n; i++) {  
       arr[i] += min;  
    }  
}  
  
// 返回number在BASE进制下有几位  
public static int bits(int number) {  
    int ans = 0;  
    while (number > 0) {  
       ans++;  
       number /= BASE;  
    }  
    return ans;  
}  
  
// 基数排序核心代码  
// arr内要保证没有负数 // 要是有负数, 就将数组中所有的数字加上数组中的最小值, 要是溢出了就用long类型.  
// m是arr中最大值在BASE进制下有几位, BASE的意思是进制的意思, 此时我们先将BASE理解为 10 进制.  
public static void radixSort(int bits) {  
    // 理解的时候可以假设BASE = 10  
    for (int offset = 1; bits > 0; offset *= BASE, bits--) { // bits就代表位数, 而且是所有数字中最大的位数.  
       Arrays.fill(cnts, 0); // 先将cnts数组的所有数字都填充为0.用来为以后记录每一个位数出现的次数做铺垫.  
       for (int i = 0; i < n; i++) {  
          // 数字提取某一位的技巧  
          cnts[(arr[i] / offset) % BASE]++; // 将个位数字对应的数字提取出来然后将数组对应的数字++.  
       }  
       // 处理成前缀次数累加的形式  
       for (int i = 1; i < BASE; i++) {  
          cnts[i] = cnts[i] + cnts[i - 1];  
       }  
       for (int i = n - 1; i >= 0; i--) {  // 一定要从右往左遍历,  
          // 前缀数量分区的技巧  
          // 数字提取某一位的技巧  
          help[--cnts[(arr[i] / offset) % BASE]] = arr[i];  
       }   // 根据每一个数字不同位数的值放到help数组中, 将cnts数组中对应的位置的数字 “--” 之后, 将arr数组中的数字放到help数组中.  
       for (int i = 0; i < n; i++) {  
          arr[i] = help[i];   // 将help数组中数字刷回到arr数组中.  
       }  
    }  
}

3.1 分析复杂度

时间复杂度:O(n).
空间复杂度:O(m). m 指的是自己要决定的进制. 就是需要几个桶.

4. 计数排序的代码实例

public class CountingSort {
    public static void main(String[] args) {
        int[] arr = {4, 2, 2, 8, 3, 3, 1};
        System.out.println("原始数组: " + Arrays.toString(arr));

        countingSort(arr);
        System.out.println("排序后的数组: " + Arrays.toString(arr));
    }

    public static void countingSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return; // 如果数组为空,直接返回
        }

        // 找到数组中的最大值
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }

        // 创建计数数组
        int[] count = new int[max + 1];

        // 统计每个元素的出现次数
        for (int num : arr) {
            count[num]++;
        }

        // 将计数数组中的元素按顺序填回原数组
        int index = 0;
        for (int i = 0; i < count.length; i++) {
            while (count[i] > 0) {
                arr[index++] = i; // 根据计数填充原数组
                count[i]--;
            }
        }
    }
}

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

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

相关文章

Redis string类型hash类型

string类型 类型介绍 在Redis中的所有的key都是string类型&#xff0c;而value的类型有多种。 Redis中的字符串是直接按照二进制的方式进行存储的&#xff0c;也就是不会做任何的编码转换&#xff0c;存的是什么&#xff0c;取出来的就是什么。这样一般来说&#xff0c;Redi…

《程序猿之Redis缓存实战 · 位图类型》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

设计效率拉高!一键生成国庆主题3d立体字!

​ 一键生成国庆主题字&#xff01;这是数字75&#xff0c;上传&#xff0c;点击生成 等个几秒&#xff0c;75周年立体字就做好了 而且融入了各种中国古建筑元素 这样的国庆主题字效果&#xff0c;以前我们要用c4d建模然后渲染出效果图 费时费力才做出一张 现在我们工作流…

事实与价值双阈值是算计启动的门槛

在现代社会&#xff0c;个体与群体的决策过程受到多种因素的影响&#xff0c;其中事实与价值的关系尤为重要。事实作为客观存在的基础&#xff0c;价值则是主观认知的体现。两者的相互作用构成了人类行为的复杂性&#xff0c;尤其在经济学、社会学以及伦理学等领域&#xff0c;…

JAVA毕业设计182—基于Java+Springboot+vue3的河道治理管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的河道治理管理系统(源代码数据库)182 一、系统介绍 本项目前后端分离(可以改为ssm版本)&#xff0c;分为用户、工作人员、管理员三种角色 1、用户&#x…

linux 系统磁盘空间查看与清理

正常清理步骤 首先查看文件和目录的使用空间&#xff0c;系统/根目录下的文件夹一般情况不会占用大的磁盘空间&#xff0c;因此可主要查看您创建的目录或文件等 文件大小 使用ls -alh命令来查看&#xff0c;比如下方的.bashrc、.profile文件的大小。但是看到的文件夹大小仅仅…

Spire.PDF for .NET【页面设置】演示:设置 PDF 的查看器首选项和缩放系数

优化查看器首选项和缩放因子对于改善 PDF 文档的查看体验至关重要。通过使用适当的查看器首选项和缩放因子&#xff0c;您可以使您的 PDF 文档更加用户友好、可查看且适合不同的设备和平台。在本文中&#xff0c;我们将演示如何使用Spire.PDF for .NET在 C# 和 VB.NET 中为 PDF…

【计算机网络】详解HTTP请求和响应格式常见请求方法Header报头响应报文状态码URL

一、HTTP协议的定义 在互联网世界中&#xff0c;HTTP &#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一个至关重要的协议。它定义了客户端&#xff08;如浏览器&#xff09;与服务器之间如何通信&#xff0c;以交换或传输超文本&#xff08…

毕业设计选题:基于springboot+vue+uniapp的在线办公小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

召回11 地理位置召回、作者召回、缓存召回

有用但重要性不高 地理位置召回 GeoHash召回&#xff1a;对身边周围的事情感兴趣 GeoHash把经纬度编码成二进制哈希码方便检索。召回只根据经纬度这个地理位置&#xff0c;返回一批优质笔记&#xff0c;完全不考虑用户兴趣&#xff0c;也是因此返回优质笔记&#xff0c;大概…

[Docker学习笔记]Docker的原理Docker常见命令

文章目录 什么是DockerDocker的优势Docker的原理Docker 的安装Docker 的 namespaces Docker的常见命令docker version:查看版本信息docker info 查看docker详细信息我们关注的信息 docker search:镜像搜索docker pull:镜像拉取到本地docker push:推送本地镜像到镜像仓库docker …

安卓13设置删除网络和互联网选项 android13隐藏设置删除网络和互联网选项

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改4.1修改方法14.2修改方法25.编译6.彩蛋1.前言 有些客户不想让用户修改默认的网络配置,禁止用户进入里面调整网络相关的配置。 2.问题分析 像这个问题,我们有好几种方法去处理,这种需求一般…

PyGWalker:让你的Pandas数据可视化更简单,快速创建数据可视化网站

1、PyGWalker应用: 在数据分析的过程中,数据的探索和可视化是至关重要的环节,如何高效地将分析结果展示给团队、客户,甚至是公众,是很多数据分析师和开发者面临的挑战,接下来介绍的两大工具组合——PyGWalker与Streamlit,可以帮助用户轻松解决这个问题,即使没有复杂的代…

java 洛谷题单【数据结构1-1】线性表

P3156 【深基15.例1】询问学号 解题思路 很简单的一道题&#xff0c;但是由于n、m的数据很大&#xff0c;要用Java的I/O流读入和输出。 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.StreamTokenizer; impo…

【springboot】使用thymeleaf模板

1. 导入依赖 首先&#xff0c;创建一个Spring Boot项目&#xff0c;并添加Thymeleaf依赖。在pom.xml文件中添加以下依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifa…

9.28 daimayuan 模拟赛总结

感觉 -S 模拟赛时间好紧啊 复盘 8:00 开题 扫了一遍四道题&#xff0c;感觉 T1 很典&#xff0c;T2 有点神秘&#xff0c;T3 计数&#xff0c;但限制是简单的&#xff0c;看上去非常可做&#xff1b;T4 也有点神秘 推 T1&#xff0c;先定根&#xff0c;然后树形dp是显然的&…

【Android】Jetpack组件之LifeCycle

引言 Lifecycle组件是Android Jetpack架构组件之一&#xff0c;它提供了一种方法来管理Android组件&#xff08;如Activity、Fragment和服务&#xff09;的生命周期。Lifecycle组件帮助你执行与生命周期相关联的操作&#xff0c;确保在适当的时间发生适当的事情&#xff0c;例…

服务器几核几G几M是什么意思?如何选择?

服务器几核几G几M是什么意思&#xff1f;我们建站、搭建网络平台都要用到云服务器&#xff0c;不管在腾讯云、阿里云还是别的云服务平台选购&#xff0c;都会接触到服务器配置。云服务器就是把物理服务器&#xff08;俗称“母鸡”&#xff09;&#xff0c;用虚拟机技术虚拟出多…

LeetCode: 1971. 寻找图中是否存在路径

寻找图中是否存在路径 原题 有一个具有 n 个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点…

mips指令系统简介

**MIPS&#xff08;Microprocessor without Interlocked Piped Stages&#xff09;**&#xff1a;这是一种RISC&#xff08;精简指令集计算&#xff09;芯片架构&#xff0c;由John L. Hennessy设计&#xff0c;特点是没有内部互锁的流水级&#xff0c;简化了处理器设计。 对比…