计数排序,基数排序,桶排序

news2024/9/27 17:23:15

目录

计数排序:

基数排序:

桶排序:

计数排序:

计数排序是一种非比较型整数排序算法,特别适用于一定范围内的整数排序。它的核心思想是使用一个额外的数组(称为计数数组)来计算每个值的出现次数,然后根据这些计数信息将所有的数字放到正确的位置上,从而实现排序。

大致流程就是这样的,我会用图和代码的思想给大家讲清楚的:

  1. 确定范围:首先遍历一遍待排序的数组,找出其中最大值和最小值,从而确定数字的范围。这是因为计数排序要求输入数据在一个已知的有限范围内。

  2. 初始化计数数组:创建一个长度为最大值与最小值之差加一的计数数组,并将所有元素初始化为0。计数数组的索引代表原始数组中的数值,值则表示该数值出现的次数。

  3. 计数:再次遍历原始数组,对于每个元素,将计数数组中对应索引的值加一,即统计每个元素出现的次数。

  4. 累加计数:对计数数组进行累加操作,使得计数数组中的每个元素变成小于等于其索引值的所有元素的累计总和。这样,计数数组中的每个位置就表示了在排序后数组中相应数值的起始位置。

  5. 重建数组:最后,从计数数组反向遍历原始数组,根据计数数组的信息将元素放到排序后数组的正确位置上。当把一个元素放到排序后数组时,相应地减少计数数组中该元素的计数,以保持计数的准确性。

第一步:创建一个数组,并且找到其中的最大值和最小值

第二步: 定义一个count数组(最大值 减去 最小值 加一),用来统计每个数字出现的次数

 第三步:遍历count数组,然后按照大小顺序把依次放回array数组里面去(可以理解成覆盖了原数组),最后出来的结果就可以按照大小顺序看到每一个数字出现的多少次

最后出来的结果就是:[1, 1, 2, 3, 4, 5, 5, 6, 8, 9, 9]

public static int[] countSort(int[] array){
        int minval = array[0];
        int maxval = array[0];
        for(int i = 0;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++){
            int val = array[i];
            count[val-minval]++;
        }
        //遍历计数数组
        int index = 0;
        for(int i = 0;i<count.length;i++){
            while(count[i]>0){
                array[index] = i+minval;
                index++;
                count[i]--;
            }
        }
        return array;
    }

 

时间复杂度:

  • 计数排序的时间复杂度主要由两部分组成:统计每个元素出现次数和根据统计结果重构输出数组。对于n个元素,范围在0到k之间的整数排序,计数排序的时间复杂度为O(n+k)。其中,n是数组长度,k是数组中最大值与最小值的差加1。在最好的情况下(即k接近n或n),时间复杂度接近线性,但在k远大于n时,时间复杂度会显著增长。

空间复杂度:

  • 计数排序需要额外的空间来存储每个元素的计数,这个空间取决于待排序数组中元素的范围。具体来说,需要一个大小为k+1的计数数组,其中k是数组中的最大值与最小值之差加1。因此,空间复杂度为O(k)。如果k与n同数量级,那么空间复杂度也是线性的,即O(n)。

稳定性:

  • 计数排序是一种稳定的排序算法。因为它在统计每个元素的频率之后,按照元素原来的顺序(通过第二个循环从最小元素开始逐个累加计数数组并放回原数组)将元素放回原数组,保证了相同元素的相对位置不会改变。

基数排序:

它的核心思想是将待排序的元素根据其每一位的数值进行分配,这个过程通常从最低有效位(LSD)开始,也可以从最高有效位(MSD)开始,然后按位递进,直到最高位。基数排序利用了分配式排序的策略,也被称为“桶排序”的一种推广。


第一步:首先得有一个数组然后才能对数组进行排序,找到数组中的最大值,确定最大值的位数,比如198就是3位数

第二步:新建一个buckets数组,根据个位十位百位...来存放数据

1.按照个位数排序就是这样的,然后我们按照先进先出的原则拿出来

 

2.此时的buckets数组就是:

 

3.因为最大为2位数,所以我们还得再进行一个十位的比较

4. 按照先进先出的原则拿出来就得到最终结果

private static int getMaxDigits(int[] array){
    int maxnum = array[0];
    for(int num : array){
        if(num > maxnum){
            maxnum = num;
        }
    }
    return (int)Math.log10(maxnum)+1;
}

public static int[] radixSort(int[] array){
    int maxnum = getMaxDigits(array);
    int radix = 10;//基数是10 因为是10进制
    List<Integer>[] buckets = new ArrayList[radix];
    for(int i = 0;i < radix;i++){
        buckets[i] = new ArrayList<>();
    }

    for(int digit = 1;digit <= maxnum;digit++){

        //记得每次要把桶清空
        for (List<Integer> bucket : buckets) {
            bucket.clear();
        }

        for (int num : array) {
            int index = (num / (int)Math.pow(10,digit-1))%10;
            buckets[index].add(num);
        }

        //从桶中收集元素到数组
        int index = 0;
        for(List<Integer> bucket : buckets){
            for(int num : bucket){
                array[index] = num;
                index++;
            }
        }
    }
    return array;
}

 

时间复杂度:

  • 基数排序的时间复杂度主要取决于数字的位数(d,即最大数的位数)和待排序元素的数量(n)。
  • 最好、平均和最坏情况下,基数排序的时间复杂度都是O(d*(n+k)),其中k是基数(通常是10,因为基数排序常用于十进制数)。这意味着排序的总成本是元素数量乘以位数,加上每一轮分配和收集过程中桶的管理开销。当d和k相对于n较小或固定时,基数排序非常高效。

空间复杂度:

  • 空间复杂度主要来自于存储桶(或列表)的需要。基数排序需要额外的空间来存放每个基数下的元素。最坏情况下,每个元素都会进入不同的桶中,因此空间复杂度为O(n+k)。但实际上,由于基数排序是分轮进行的,每轮只需要足够的空间来存放每个桶中的元素,理想情况下空间复杂度可以近似为O(n)。但是,考虑到实际实现中可能会为每一轮都分配桶空间,总的空间复杂度还是O(n+k)。

稳定性:

  • 基数排序是稳定的排序算法。这意味着相等的元素在排序前后相对位置保持不变。这是因为基数排序是按位进行的,每一趟排序都是独立的,并且在收集阶段按照原顺序从桶中取出元素,从而保持了稳定性。

桶排序:

  1. 初始化桶:首先确定桶的数量和每个桶覆盖的数值范围。桶的数量和分布取决于待排序数据的特性,通常需要预先知道数据的大致分布情况。

  2. 分配元素到桶:遍历待排序数组,根据元素的值将它们分配到对应的桶中。分配的依据可以是元素的大小,例如,如果数据范围是0到100,可以创建10个桶,每个桶负责10个数的范围。

  3. 桶内排序:对每个非空的桶内部进行排序。可以选择插入排序、快速排序等算法,具体选择取决于桶内元素的数量和特性。

  4. 合并桶:将所有桶中的元素按照桶的顺序(通常是桶的索引)依次取出,合并到一个数组中,这样合并后的数组就是有序的。

 

代码实现: 

public static int[] bucketSort(int[] arr){

        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for(int num : arr){
            max = Math.max(max,num);
            min = Math.min(min,num);
        }

        //初始化桶
        int bucketnum = (max-min)/arr.length+1;
        List<Integer>[] buckets = new ArrayList[bucketnum];
        for (int i = 0; i < bucketnum; i++) {
            buckets[i] = new ArrayList<>();
        }

        //将每个元素放入桶中
        for (int i = 0; i < arr.length; i++) {
            int index = (arr[i] - min) * (bucketnum - 1) / (max - min);
            buckets[index].add(arr[i]);
        }

        //对桶里面的每个元素进行排序,这里可以自己实现其他排序算法
        for (int i = 0; i < buckets.length; i++) {
            Collections.sort(buckets[i]);
        }

        //重新将桶中的每个元素放回到原数组中
        int index = 0;
        for (int i = 0; i < buckets.length; i++) {
            for(int j = 0;j<buckets[i].size();j++){
                arr[index] = buckets[i].get(j);
                index++;
            }
        }
        return arr;

    }

时间复杂度:

  • 最好情况:如果数据均匀分布在各个桶中,且桶内排序所用的算法具有良好的时间复杂度(如插入排序在小数组上接近O(n)),桶排序的整体时间复杂度可以达到O(n + k),其中n是待排序元素的数量,k是桶的数量。
  • 平均情况:同样,如果数据分布较为均匀,桶排序的时间复杂度也是O(n + k)。
  • 最坏情况:如果所有数据都集中在少数几个桶中,特别是全部集中在同一个桶里,此时桶排序的时间复杂度退化,需要对这些桶内的元素进行排序,可能会达到O(n^2),这取决于桶内排序算法的时间复杂度。

空间复杂度:

  • 桶排序的空间复杂度主要取决于桶的数量和每个桶可能存储的元素数量。最坏情况下,如果每个元素都分配到了不同的桶中,空间复杂度为O(n)加上每个桶的额外开销。通常,空间复杂度为O(n + k),其中k是桶的数量,n是数组的长度。如果桶的数量k与n成正比或者接近n,那么空间复杂度接近O(n)。

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

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

相关文章

Nginx 从入门到实践(2)——Rewrite重写

Nginx Rewrite Rewrite重写 Nginx Rewriteurl组成说明Rewrite基本概述Rewrite使⽤场景rewrite优点 Rewrite配置语法location匹配概述 if指令if 判断指令语法nginx以及if 判断可使用的全局变量 set命令return指令 url组成说明 https://cn.bing.com/search?qNginxRewrite&P…

VMware虚拟机忘记密码重置--centos7x

centos7虚拟机重置root密码 操作流程 操作流程 重启虚拟机&#xff0c;在如下页面键入e键&#xff1a; 2. 在如下位置添加&#xff1a;init/bin/sh&#xff0c;添加完成后键入Ctrlx启动 依次键入下列命令&#xff1a; mount -o remount,rw / #重新挂载/目录 passwd root #修改…

【微服务】分布式事务(通过Seata解决分布式事务问题)

分布式事务 分布式事务Seata微服务集成SeataXA模式XA模式使用 AT模式AT模式实现 分布式事务 在分布式系统中&#xff0c;如果一个业务需要多个服务合作完成&#xff0c;而且每一个服务都有事务&#xff0c;多个事务必须同时成功或失败&#xff0c;这样的事务就是分布式事务&am…

选择深度学习框架:TensorFlow 2 vs PyTorch

TensorFlow 2 vs PyTorch 选择深度学习框架&#xff1a;TensorFlow 2 vs PyTorchTensorFlow 2概述TensorFlow 2的优点TensorFlow 2的缺点 PyTorch概述PyTorch的优点PyTorch的缺点 选择建议对于选择困难症的人&#xff0c;我给你们的答案——PyTorch选择理由&#xff1a;结论&am…

Mac 电脑安装 Raptor 流程图软件的方法

0. 安装逻辑 &#xff08;1&#xff09;运行 raptor&#xff0c;本质上需要 mac 能够运行 windows 程序&#xff0c;因此需要安装 .NET Runtime 7.0&#xff0c;这是微软程序运行必须的文件。 &#xff08;2&#xff09;运行 raptor 还需要安装依赖文件 mono-libgdiplus。 &am…

python从0开始学习(三)

目录 前言 1、类型转换 1.1 隐式类型转换 1.2 显式类型转换 2、eval函数 总结 前言 上篇我们讲了python中的变量与常量&#xff0c;以及变量类型。本篇文章将接着往下讲。 1、类型转换 python中的数据类型转换包括两种&#xff1a;隐式类型转换和显式类型转换。 1.1 隐式…

微信IDE vscode插件:获取插件位置,并打开文件

背景 有没有觉得在微信开发工具里面添加一些插件可以很方便。因为微信IDE的编辑本身是依赖vscode开发&#xff0c;所以编写vscode插件自然可以在微信IDE使用。这样做好处就是可以满足到自己一些开发使用习惯。 1.获取插件的目录位置 那么如何获取插件里面的目录&#xff0c;…

分布式领域计算模型及SparkRay实现对比

目录 一、分布式计算领域概览 二、Spark计算模型分析 三、Ray计算模型分析 3.1 需求分析 3.2 系统设计 3.3 系统实现 四、总结 一、分布式计算领域概览 当前分布式计算模型主要分为以下4种&#xff1a; Bulk Synchronous Parallel Model&#xff08;块同步并行模型&…

常用语音识别开源四大工具:Kaldi,PaddleSpeech,WeNet,EspNet

无论是基于成本效益还是社区支持&#xff0c;我都坚决认为开源才是推动一切应用的动力源泉。下面推荐语音识别开源工具&#xff1a;Kaldi&#xff0c;Paddle&#xff0c;WeNet&#xff0c;EspNet。 1、最成熟的Kaldi 一个广受欢迎的开源语音识别工具&#xff0c;由Daniel Pove…

开发体育赛事直播平台,研发技术选型与架构设计实现方案

本文将深入探讨“东莞梦幻网络科技”现成体育直播源码的技术实现方案&#xff0c;如何为用户提供流畅、互动、个性化的观赛体验。 一、技术栈选择&#xff1a;强强联合的基石1、后端开发&#xff1a;采用Java与PHP作为主要开发语言。Java以其强大的企业级应用支持&#xff0c;保…

C++证道之路第十七章输入输出和文件

一、C输入和输出概述 C 提供了丰富的输入/输出&#xff08;I/O&#xff09;功能&#xff0c;这些功能主要通过 <iostream> 头文件中的类和对象来实现。 1.流和缓冲区 C把程序输入和输出看作字节流。输入时&#xff0c;程序从输入流中抽取字节&#xff1b;输出时&#…

使用 FFmpeg 从音视频中提取音频

有时候我们需要从视频文件中提取音频&#xff0c;并保存为一个单独的音频文件&#xff0c;我们可以借助 FFmpeg 来完成这个工作。 一、提取音频&#xff0c;保存为 mp3 文件: 要使用 FFmpeg 从音视频文件中提取音频&#xff0c;并将 ACC 编码的音频转换为 MP3 格式&#xff0…

【数据结构(邓俊辉)学习笔记】列表02——无序列表

文章目录 0.概述1.插入与构造1.1 插入1.1.1 前插入1.1.2后插入1.1.3 复杂度 1.2 基于复制构造1.2.1 copyNodes()1.2.2 基于复制构造1.2.3 复杂度 2.删除与析构2.1 删除2.1.1 实现2.1.2 复杂度 2.2 析构2.2.1 释放资源及清除节点2.2.2 复杂度 3.查找3.1 实现3.2 复杂度 4.唯一化…

FFmpeg学习记录(四)——SDL音视频渲染实战

1.SDL使用的基本步骤 SDL Init/sDL _Quit()SDL_CreateWindow()/SDL_DestoryWindow()SDL CreateRender() SDL_Windows *windows NULL;SDL_Init(SDL_INIT_VIDEO);window SDL_CreateWindow("SDL2 Windows",200,200, 640,480,SDL_WINDOW_SHOWN);if(!window) {printf(&…

【C语言回顾】数据在内存中的存储

前言1. 概述2. 大小端字节序和字节序判断2.1 大端字节序&#xff08;Big-Endian&#xff09;2.2 小端字节序&#xff08;Little-Endian&#xff09;2.3 判断字节序的示例 3. 数据在内存中的存储3.1 整数在内存中的存储3.2 浮点数在内存中的存储 结语 ↓ 上期回顾: 【C语言回顾】…

STM32 01

1、编码环境 1.1 安装keil5 1.2 安装STM32CubeMX 使用STM32CubeMX可以通过界面的方式&#xff0c;快速生成工程文件 安装包可以从官网下载&#xff1a;https://www.st.com/zh/development-tools/stm32cubemx.html#overview 安装完要注意更新一下固件包的位置&#xff0c;因为…

A股上市公司财务松弛数据集(2000-2022年)

01、数据介绍 财务松弛是指企业在运营过程中&#xff0c;由于各种原因导致其财务状况出现一定程度的松弛或宽裕状态。这种状态通常表现为企业持有较多的现金和流动性资产&#xff0c;同时负债相对较少&#xff0c;或者企业有较多的未使用授信额度等。 本数据包括&#xff1a;…

伺服电机初识

目录 一、伺服电机的介绍二、伺服电机的基本原理三、伺服电机的技术特点四、伺服电机的分类五、实际产品介绍1、基本技术规格&#xff1a;2、MD42电机硬件接口3、通讯协议介绍3.1 通讯控制速度运行3.2 通讯控制位置运行3.3 通讯控制转矩运行 4、状态灯与报警信息 一、伺服电机的…

C语言之整形提升和算术转换

目录 前言 一、整形提升 二、算术转换 总结 前言 本文主要介绍C语言中的整形提升和算术转换的概念和意义&#xff0c;以及例题帮助理解&#xff0c;了解之后&#xff0c;我们就能知道在C语言中&#xff0c;字符型变量如何计算以及如果变量的类型、字节大小不一致的情况下&am…

JVM组成之类加载器

类加载器&#xff08;ClassLoader&#xff09;&#xff1a;是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。 类加载器多数是有Java编写的&#xff0c;也有部分是c编写的&#xff0c;负责接收来自外部的二进制数据&#xff0c;然后执行JNI&#xff08;也就是本…