【排序算法】Java实现三大非比较排序:计数排序、桶排序、基数排序

news2024/9/17 9:00:16

非比较排序概念

非比较排序是一种排序算法,它不通过比较元素之间的大小关系来进行排序,而是基于元素的特征或属性进行排序。这种方法在特定情况下可以比比较排序方法(如快速排序、归并排序等)更有效率,尤其是在处理大量数据时。非比较排序通常要求输入数据满足一定的条件,或者对数据的特征进行合理的利用。

常见的非比较排序算法包括:

  1. 计数排序(Counting Sort)

    • 适用于范围较小的整数排序。通过统计每个元素出现的次数,然后将元素按顺序放入结果数组。
  2. 桶排序(Bucket Sort)

    • 将数据分到若干个桶中,随后对每个桶中的数据进行排序,最后再将所有桶中的数据合并在一起。
  3. 基数排序(Radix Sort)

    • 通过将待排序的数值按位数分组,逐位进行排序,通常配合计数排序实现。

非比较排序的时间复杂度通常可以达到 O(n) ,但它们一般要求算法的输入数据具有特定的特征,且通常是稳定的排序算法。非比较排序的优势在于在适当的条件下,它可以显著提高排序的效率。

计数排序

计数排序是一种非比较型的排序算法,适用于特定条件下的排序,尤其是当待排序的元素范围较小且其重复元素较多时。它的基本原理是利用一个额外的数组来记录每个元素出现的次数,从而达到排序的目的。以下是计数排序的原理步骤:

  1. 确定范围:首先确定待排序数组中元素的值的范围,找到最大值和最小值。

  2. 创建计数数组:根据范围创建一个计数数组,数组的大小通常为最大值与最小值的差加一,用于存放每个元素的出现次数。

  3. 计数:遍历原始数组,对每个元素在计数数组中对应的位置进行计数。即,若元素为 x,则计数数组的第 x 位置的值加一。

  4. 计算位置:通过累加计数数组的值,得到每个元素在已排序数组中的最终位置。这一步实际上是将计数数组转换为位置数组。

  5. 排序输出:根据计数数组生成已排序数组。遍历计数数组,按次数将对应的元素输出到结果数组中。

计数排序的时间复杂度为 O(n + k),其中 n 是待排序元素的数量,k 是计数数组的大小。因其效率较高,通常适用于待排序数据范围较小的情况。

代码实现: 

public class CountSort {
    public static void sort(int[] array) {
        int minVal = array[0];
        int maxVal = array[0];
        //遍历数组找到最小值和最大值
        for (int i = 1; i < array.length; i++) {
            if(array[i] < minVal) {
                minVal = array[i];
            }
            if(array[i] > maxVal) {
                maxVal = array[i];
            }
        }
        //构建计数数组
        int[] count = new int[maxVal-minVal+1];
        for (int i = 0; i < array.length; i++) {
            count[array[i]-minVal]++;
        }
        int index = 0;
        //将计数数组中的元素还原到原数组中
        for (int i = 0; i < count.length; i++) {
            while (count[i] > 0) {
                array[index] = i + minVal;
                index++;
                count[i]--;
            }
        }
    }
}

桶排序

桶排序是一种基于分桶思想的排序算法,适用于数据分布比较均匀的情况。它的基本原理是将待排序的元素分散到一定数量的“桶”里,每个桶内部再进行排序,最后将所有桶中的元素合并在一起。以下是桶排序的具体步骤:

  1. 创建桶:根据待排序数据的范围和数量,创建一定数量的桶。每个桶的范围可以根据数据的分布情况进行划分。

  2. 分配元素:将待排序的元素逐一放入相应的桶中。每个元素根据其值决定放入哪个桶。例如,如果某个桶的范围是 [a, b),那么落在这个范围内的元素就会放入该桶。

  3. 排序桶内元素:对每个非空的桶内部进行排序。可以使用其他排序算法(如插入排序、快速排序等)对每个桶内的元素进行排序。

  4. 合并桶:将排序好的每个桶中的元素按顺序合并成一个新的有序数组,完成整个排序过程。

桶排序的时间复杂度通常为 O(n),最佳情况发生在数据均匀分布时,但在最坏情况下,时间复杂度可以退化为 O(n^2),当所有元素都落在同一个桶中时。桶排序的空间复杂度为 O(n + k),其中 n 是待排序元素的数量,k 是桶的数量。

总的来说,桶排序在适合的场景下能提供非常优秀的性能,尤其是在处理大量数据时具有明显优势。

import java.util.ArrayList;
import java.util.Collections;

public class BucketSort {

    // 主排序方法
    public static void bucketSort(float[] arr) {
        // 如果数组为空或只有一个元素,直接返回
        if (arr == null || arr.length <= 1) {
            return;
        }

        // 1. 找出数组中的最大值和最小值
        float maxValue = arr[0];
        float minValue = arr[0];
        for (float num : arr) {
            if (num > maxValue) {
                maxValue = num;
            }
            if (num < minValue) {
                minValue = num;
            }
        }

        // 2. 计算桶的数量
        int bucketCount = (int) Math.floor(maxValue - minValue) + 1; // 桶的数量
        ArrayList<ArrayList<Float>> buckets = new ArrayList<>(bucketCount);

        // 3. 初始化每个桶
        for (int i = 0; i < bucketCount; i++) {
            buckets.add(new ArrayList<>()); // 创建一个空的桶
        }

        // 4. 将元素分配到桶中
        for (float num : arr) {
            int bucketIndex = (int) (num - minValue); // 计算索引
            buckets.get(bucketIndex).add(num); // 将元素放入对应的桶中
        }

        // 5. 对每个桶内部进行排序并合并
        int index = 0; // 记录排序后的数组的索引
        for (ArrayList<Float> bucket : buckets) {
            Collections.sort(bucket); // 使用 Collections.sort() 对桶内部元素进行排序
            for (float num : bucket) {
                arr[index++] = num; // 将排序后的元素放回原数组
            }
        }
    }

    // 测试代码
    public static void main(String[] args) {
        float[] arr = {0.42f, 0.32f, 0.33f, 0.52f, 0.37f, 0.51f, 0.32f};
        System.out.println("原始数组:");
        for (float num : arr) {
            System.out.print(num + " ");
        }
        
        // 调用桶排序
        bucketSort(arr);

        System.out.println("\n排序后的数组:");
        for (float num : arr) {
            System.out.print(num + " ");
        }
    }
}

代码说明:

  1. 桶的创建:首先确定待排序数组的最大值和最小值,然后根据这个范围创建相应数量的桶。
  2. 元素分配:遍历原始数组,将每个元素放入对应的桶中,桶的索引由元素的值与最小值的差决定。
  3. 桶内排序:对每个桶内部进行排序,这里使用了 Java 提供的 Collections.sort() 方法。
  4. 合并结果:将每个桶中排序后的元素放回原数组。

运行上述代码,可以验证桶排序的功能,并输出原始数组与排序后的数组。

基数排序

基数排序是一种非比较型的排序算法,主要用于整数排序,但也可以扩展到字符串等数据类型。它的基本原理是将待排序的数值分解成不同的位数,然后按位进行排序,通常使用稳定的排序算法(如计数排序)对每一位进行排序。以下是基数排序的基本步骤:

  1. 确定最大位数:首先找出待排序数据中最大数的位数,以确定需要进行多少轮排序。

  2. 从低到高位排序:从最低位开始,对所有数字进行排序。在每一轮中,将所有数字按照当前位的值分配到对应的桶中,然后再依次收集在一起。这个过程使用稳定的排序算法来保证相同数字的相对顺序不变。

  3. 重复进行:对每一位重复排序,直到最高位。

  4. 得到最终排序结果:经过多次按位排序后,所有元素会按照升序排列。

基数排序的时间复杂度为 O(n * k),其中 n 是待排序的元素数量,k 是最大数字的位数。基数排序的空间复杂度为 O(n + k),主要用于存放桶和临时数组。

基数排序特别适合处理大量数据且范围不是特别大的整数,能够在特定条件下达到很高的效率。

import java.util.Arrays;

public class RadixSort {

    // 基数排序主方法
    public static void radixSort(int[] arr) {
        // 1. 找到最大值,以确定排序的位数
        int max = findMax(arr);
        
        // 2. 对每一位进行排序
        for (int exp = 1; max / exp > 0; exp *= 10) {
            countingSortByDigit(arr, exp);
        }
    }

    // 找到数组中的最大值
    private static int findMax(int[] arr) {
        int max = arr[0];
        for (int num : arr) {
            if (num > max) {
                max = num;
            }
        }
        return max;
    }

    // 根据当前位进行计数排序
    private static void countingSortByDigit(int[] arr, int exp) {
        int n = arr.length;
        int[] output = new int[n]; // 输出数组
        int[] count = new int[10]; // 计数数组,范围是 0-9

        // 1. 初始化计数数组
        Arrays.fill(count, 0);

        // 2. 统计每个数字在当前位(exp)上的出现次数
        for (int i = 0; i < n; i++) {
            int digit = (arr[i] / exp) % 10; // 取出当前位的数字
            count[digit]++;
        }

        // 3. 计算每个元素的最终位置
        for (int i = 1; i < 10; i++) {
            count[i] += count[i - 1]; // 计算累计频率
        }

        // 4. 从后往前填充输出数组,保证稳定性
        for (int i = n - 1; i >= 0; i--) {
            int digit = (arr[i] / exp) % 10; // 取出当前位的数字
            output[count[digit] - 1] = arr[i]; // 放置到输出数组
            count[digit]--; // 减少计数
        }

        // 5. 将排序好的数据复制回原数组
        System.arraycopy(output, 0, arr, 0, n);
    }

    // 测试代码
    public static void main(String[] args) {
        int[] arr = {170, 45, 75, 90, 802, 24, 2, 66};
        System.out.println("原始数组:");
        System.out.println(Arrays.toString(arr));

        // 调用基数排序
        radixSort(arr);

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

代码说明:

  1. 找到最大值findMax 方法用来找到数组中的最大值,以确定需要进行多少次排序。
  2. 按位排序radixSort 方法从最低位开始,每次调用 countingSortByDigit 方法进行计数排序。
  3. 计数排序实现countingSortByDigit 方法中的步骤:
    • 初始化一个计数数组 count,用于记录每个数字在当前位上的出现次数。
    • 统计每个数字对当前位的贡献并存储到计数数组中。
    • 根据计数数组生成输出数组,确保排序的稳定性。
    • 将输出数组中的元素复制回原数组中。
  4. 测试代码:在 main 方法中定义一组整数,调用 radixSort 方法进行排序,并输出排序前后的结果。

运行上述代码可以验证基数排序的功能,并观察原始数组与排序后的数组。

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

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

相关文章

时间序列分析方法之 -- 长短期记忆网络(LSTM)原理及Python代码示例

目录 原理 适用情况 Python示例代码 结论 原理 长短期记忆网络&#xff08;LSTM&#xff0c;Long Short-Term Memory Networks&#xff09;是一种特殊的递归神经网络&#xff08;RNN&#xff09;&#xff0c;设计用于克服传统RNN在处理长序列数据时的梯度消失和梯度爆炸问…

如何在基于滤波框架的绝对定位系统中融合相对观测

文章目录 1 LIO、VIO propagation来代替IMU propagation2 TRO paper: Stochastic Cloning Kalman filter【有待填坑】 以无人驾驶定位系统为例&#xff0c;融合gnss&#xff0c;imu&#xff0c;轮速&#xff0c;camera LaneMatch(frame to map)&#xff0c;lidar scan match(fr…

Qgis 插件升级:3.28到3.34

1、下载 osgeo4w-setup 安装包 下载 获取 osgeo4w-setup.exe 的安装软件&#xff0c;每次下一步就可以了&#xff0c;安装位置可以自己调整 osgeo4w:https://download.osgeo.org/osgeo4w/osgeo4w-setup.exe 2、安装3.34 开发包 搜索qigs&#xff0c;将 desktop&#xff0c;…

开始使用OKR创建注重结果的文化

亚马逊对客户很痴迷。Facebook 行动迅速。成功企业的文化是传说中的东西&#xff0c;而且是正确的。正如管理顾问的名言&#xff1a;文化把战略当早餐吃。 无论行业或规模如何&#xff0c;文化的主要目标是激发企业获胜所需的行为。越来越多的成功要求企业关注结果而非任务&am…

Kafka的入门及简单使用

文章目录 前言一、Kafka 的基本架构&#xff1f;1. Producer&#xff08;生产者&#xff09;2. Broker&#xff08;代理/服务器&#xff09;3. Consumer&#xff08;消费者&#xff09;4. Consumer Group&#xff08;消费者组&#xff09;5. Topic&#xff08;主题&#xff09;…

docker前端部署

挂载&#xff0c;把自己的目录位置&#xff0c;挂载到容器内的HTML

万亿赛道!AI 大模型典型应用深度分析 2024

大模型由于其强大的自然语言与多模态信息处理能力&#xff0c;可以应对不同语义粒度下的任务,进行复杂的逻辑推理&#xff0c;还具有超强的迁移学习和少样本学习能力, 可以快速掌握新的任务, 实现对不同领域、不同数据模式的适配&#xff0c;这些特点使得大模型较容易的赋能其他…

吴恩达机器学习C1W2Lab05-使用Scikit-Learn进行线性回归

前言 有一个开源的、商业上可用的机器学习工具包&#xff0c;叫做scikit-learn。这个工具包包含了你将在本课程中使用的许多算法的实现。 目标 在本实验中&#xff0c;你将: 利用scikit-learn实现使用梯度下降的线性回归 工具 您将使用scikit-learn中的函数以及matplotli…

付费进群系统源码原版最新修复全开源版

付费进群&#xff0c;和平时所见到的别人拉你进群是不一样的&#xff0c;付费进群需要先缴费以后&#xff0c;才会看到群的二维码&#xff0c;扫码进群或者是长按二维码图片识别进群&#xff0c;付费进群这个功能广泛应用于拼多多的砍价群&#xff0c;活动的助力群&#xff0c;…

Chapter 21 深入理解JSON

欢迎大家订阅【Python从入门到精通】专栏&#xff0c;一起探索Python的无限可能&#xff01; 文章目录 前言一、JSON数据格式1. 什么是JSON&#xff1f;2. JSON数据的格式 二、JSON格式数据转化三、格式化JSON数据的在线工具 前言 在当今数据驱动的世界中&#xff0c;JSON&…

【大模型系列篇】Vanna-ai基于检索增强(RAG)的sql生成框架

简介 Vanna是基于检索增强(RAG)的sql生成框架 Vanna 使用一种称为 LLM&#xff08;大型语言模型&#xff09;的生成式人工智能。简而言之&#xff0c;这些模型是在大量数据&#xff08;包括一堆在线可用的 SQL 查询&#xff09;上进行训练的&#xff0c;并通过预测响应提示中最…

中间件安全:Nginx 解析漏洞测试.

中间件安全&#xff1a;Nginx 解析漏洞测试. Nginx 是一个高性能的 HTTP和 反向代理服务器&#xff0c;Nginx 解析漏洞是一个由于配置不当导致的安全问题&#xff0c;它不依赖于Nginx或PHP的特定版本&#xff0c;而是由于用户配置错误造成的。这个漏洞允许攻击者上传看似无害的…

通俗易懂,车载显示屏简单介绍!

2024年后&#xff0c;小汽车产量的年增长率预计将在1%至3%之间 2023年在COVID完全解封后&#xff0c;全球汽车销售数量提升至8千8百万台车。2024预估微幅增加到 9000万辆&#xff0c; 自2024起&#xff0c;年成长率预期将放缓至3%以下。全球汽车主要销售前三大市场 (比较2022年…

为什么阿里开发手册不建议使用Date类?

在日常编码中&#xff0c;基本上99%的项目都会有一个DateUtil工具类&#xff0c;而时间工具类里用的最多的就是java.util.Date。 大家都这么写&#xff0c;这还能有问题&#xff1f;&#xff1f; 当你的“默认常识”出现问题&#xff0c;这个打击&#xff0c;就是毁灭性的。 …

BUG解决(vue3+echart报错):Cannot read properties of undefined (reading ‘type‘)

这是 vue3echart5 遇到的报错&#xff1a;Cannot read properties of undefined (reading ‘type‘) 这个问题需要搞清楚两个关键方法&#xff1a; toRaw&#xff1a; 作用&#xff1a;将一个由reactive生成的响应式对象转为普通对象。 使用场景&#xff1a; 用于读取响应式…

idea2023 总报Low memory

idea2023 总报Low memory 问题背景问题处理 问题背景 在日常开发中&#xff0c;使用idea2023开发工具&#xff0c;开发过程中总会遇到idea提示Low memory的情况&#xff0c;并且每当提示出现的时候&#xff0c;整个idea页面便什么也不能操作了&#xff0c;如何处理这个情况呢&…

AI测试:人工智能模型的核心测试指标,分类判别、目标检测、图像分割、定量计算分别有哪些指标?

在前面的人工智能测试技术系列文章中&#xff0c;我们详细介绍了人工智能测试的技术方法和实践流程。在了解人工智能测试方法后&#xff0c;我们需要进一步学习和研究如何衡量这些方法的有效性&#xff0c;即人工智能模型测试指标的选择。测试指标的选择主要取决于模型的类型和…

借助大语言模型快速升级你的 Java 应用程序

大家都知道我爱小 Q。在我“转码”的征程中&#xff0c;它就像上帝之手&#xff0c;在我本该枯燥漫长的学习进程中拉满快进条。 不仅是我&#xff0c;最近 Amazon Q Developer 还帮助 Amazon 一个由 5 人组成的团队在短短两天内将 1,000 多个生产应用程序从 Java 8 升级到 Jav…

Spring Cloud 组件

1.eureka注册中心原理简述 1.服务注册: Eureka Client 会通过发送rest请求的方式向eureka服务端注册自身元数据:ip地址,端口,运行状况等信息,服务端会把注册信息存储在一个双层map中。 Eureka 的数据存储分了两层&#xff1a;数据存储层和缓存层。 Eureka Client 在拉取服务信息…

【STM32嵌入式系统设计与开发拓展】——13_PWM脉宽

目录 1、什么是PWM?用来做什么的&#xff1f;PWM&#xff08;Pulse Width Modulation&#xff09;脉冲宽度调制常见用到 PWM 的情况&#xff1a; 2、什么是输出比较&#xff1f;输出比较模式![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/42434920ca0940b1b1083215…