排序算法之桶排序

目录

  • 一、简介
  • 二、代码实现
  • 三、应用场景


一、简介

算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度排序方式稳定性
桶排序O(n+k )O(n+k)O(n^2)O(n+k)Out-place稳定

稳定:如果A原本在B前面,而A=B,排序之后A仍然在B的前面;
不稳定:如果A原本在B的前面,而A=B,排序之后A可能会出现在B的后面;
时间复杂度: 描述一个算法执行所耗费的时间;
空间复杂度:描述一个算法执行所需内存的大小;
n:数据规模;
k:“桶”的个数;
In-place:占用常数内存,不占用额外内存;
Out-place:占用额外内存。

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,需要做到这两点:

在额外空间充足的情况下,尽量增大桶的数量
使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
什么时候最快?
当输入的数据可以均匀的分配到每一个桶中。
什么时候最慢?
当输入的数据被分配到了同一个桶中。

算法步驟:

  • 1.确定桶的数量:根据输入数据的范围和数量确定需要多少个桶。
    首先确定桶的个数。因为桶排序最好是将数据均匀地分散在各个桶中,那么桶的个数最好是应该根据数据的分散情况来确定。首先找出所有数据中的最大值mx和最小值mn;
    根据mx和mn确定每个桶所装的数据的范围 size,有
    size = (mx - mn) / n + 1,n为数据的个数,需要保证至少有一个桶,故而需要加个1;
    求得了size即知道了每个桶所装数据的范围,还需要计算出所需的桶的个数cnt,有
    cnt = (mx - mn) / size + 1,需要保证每个桶至少要能装1个数,故而需要加个1;
  • 2.将数据分配到各个桶中:遍历输入数据,根据数据的值将数据分配到对应的桶中。
    求得了size和cnt后,即可知第一个桶装的数据范围为 [mn, mn + size),第二个桶为 [mn + size, mn + 2 * size),…,以此类推,因此步骤2中需要再扫描一遍数组,将待排序的各个数放进对应的桶中。
  • 3.对每个桶内的数据进行排序:对每个非空的桶内的数据进行排序,可以使用插入排序等方法。
  • 4.合并各个桶的数据:按照桶的顺序将各个桶内的数据合并为最终的排序结果。

示意图
元素分布在桶中:
在这里插入图片描述
然后,元素在每个桶中排序:
在这里插入图片描述


二、代码实现

 public void bucketSort(int[] nums) {
        int n = nums.length;
        int mn = nums[0], mx = nums[0];
        // 找出数组中的最大最小值
        for (int i = 1; i < n; i++) {
            mn = Math.min(mn, nums[i]);
            mx = Math.max(mx, nums[i]);
        }
        int size = (mx - mn) / n + 1; // 每个桶存储数的范围大小,使得数尽量均匀地分布在各个桶中,保证最少存储一个
        int cnt = (mx - mn) / size + 1; // 桶的个数,保证桶的个数至少为1
        List<Integer>[] buckets = new List[cnt]; // 声明cnt个桶
        for (int i = 0; i < cnt; i++) {
            buckets[i] = new ArrayList<>();//每一个桶都是一个集合
        }
        // 扫描一遍数组,将数放进桶里
        for (int i = 0; i < n; i++) {
            int idx = (nums[i] - mn) / size;
            buckets[idx].add(nums[i]);
        }
        // 对各个桶中的数进行排序,这里用库函数快速排序
        for (int i = 0; i < cnt; i++) {
            buckets[i].sort(null); // 默认是按从小打到排序
        }
        // 依次将各个桶中的数据放入返回数组中
        int index = 0;
        for (int i = 0; i < cnt; i++) {
            for (int j = 0; j < buckets[i].size(); j++) {
                nums[index++] = buckets[i].get(j);
            }
        }
    }

demo示例一下

public class BucketSort {
    public static void bucketSort(int[] nums) {
        int n = nums.length;
        int mn = nums[0], mx = nums[0];
        // 找出数组中的最大最小值
        for (int i = 1; i < n; i++) {
            mn = Math.min(mn, nums[i]);
            mx = Math.max(mx, nums[i]);
        }
        int size = (mx - mn) / n + 1; // 每个桶存储数的范围大小,使得数尽量均匀地分布在各个桶中,保证最少存储一个
        System.out.println("每个桶的储存数量大小 " + size);
        int cnt = (mx - mn) / size + 1; // 桶的个数,保证桶的个数至少为1
        System.out.println("桶的个数 " + cnt);
        List<Integer>[] buckets = new List[cnt]; // 声明cnt个桶
        for (int i = 0; i < cnt; i++) {
            buckets[i] = new ArrayList<>();//每一个桶都是一个集合
        }
        for (int i = 0; i < n; i++) {
            int number1 = mn + i * size;
            int number2 = mn + (i + 1) * size;
            System.out.println("第" + i + "个桶存理应存放数据范围 " + number1 + "----" + number2);
        }
        // 扫描一遍数组,将数放进桶里
        for (int i = 0; i < n; i++) {
            int idx = (nums[i] - mn) / size;
            buckets[idx].add(nums[i]);
            System.out.println("第" + idx + "个桶存放了" + nums[i]);
        }
        // 对各个桶中的数进行排序,这里用库函数快速排序
        for (int i = 0; i < cnt; i++) {
            buckets[i].sort(null); // 默认是按从小打到排序
        }
        // 依次将各个桶中的数据放入返回数组中
        int index = 0;
        for (int i = 0; i < cnt; i++) {
            for (int j = 0; j < buckets[i].size(); j++) {
                nums[index++] = buckets[i].get(j);
            }
        }
    }

    public static void bucketSort2(int[] nums) {
        int n = nums.length;
        int mn = nums[0], mx = nums[0];
        // 找出数组中的最大最小值
        for (int i = 1; i < n; i++) {
            mn = Math.min(mn, nums[i]);
            mx = Math.max(mx, nums[i]);
        }
        int size = (mx - mn) / n + 1; // 每个桶存储数的范围大小,使得数尽量均匀地分布在各个桶中,保证最少存储一个
        int cnt = (mx - mn) / size + 1; // 桶的个数,保证桶的个数至少为1
        List<Integer>[] buckets = new List[cnt]; // 声明cnt个桶
        for (int i = 0; i < cnt; i++) {
            buckets[i] = new ArrayList<>();
        }
        // 扫描一遍数组,将数放进桶里
        for (int i = 0; i < n; i++) {
            int idx = (nums[i] - mn) / size;
            buckets[idx].add(nums[i]);
        }
        // 对各个桶中的数进行排序,这里用库函数快速排序
        for (int i = 0; i < cnt; i++) {
            buckets[i].sort(new Comparator<Integer>() {
                @Override
                public int compare(Integer a, Integer b) {
                    return b - a; // 从大到小排序
                }
            });
        }
        // 依次将各个桶中的数据放入返回数组中
        int index = 0;
        for (int i = cnt - 1; i >= 0; i--) {
            for (int j = 0; j < buckets[i].size(); j++) {
                nums[index++] = buckets[i].get(j);
            }
        }
    }

    public static void main(String[] args) {
        int[] nums = {12, 11, 15, 50, 7, 65, 3, 99};
        System.out.println("排序前 :" + Arrays.toString(nums));
        bucketSort(nums);
        System.out.println("桶排序从小到大排序后 :" + Arrays.toString(nums));
        bucketSort2(nums);
        System.out.println("桶排序从大到小排序后 :" + Arrays.toString(nums));
    }
}

在这里插入图片描述

三、应用场景

桶排序适用于数据量较大且分布较均匀的情况,可以在一定程度上提高排序的效率。

  • 排序整数或浮点数: 桶排序适用于排序整数或浮点数,特别是在这些数的范围不是很大的情况下。通过将数分配到不同的桶中,可以在每个桶内使用其他排序算法(如插入排序、快速排序)来对桶内的数进行排序,最终将桶中的数合并得到有序序列。
  • 均匀分布的数据: 如果输入数据是均匀分布的,即数据在不同的范围内基本均匀分布,那么桶排序的效果会很好。因为桶排序将数据分配到不同的桶中,可以保证数据在各个桶中基本均匀分布,从而减少排序的复杂度。
  • 外部排序: 桶排序也适用于外部排序,即当数据量非常大,无法一次性加载到内存中进行排序时。在外部排序中,可以将数据分成若干块,每块数据可以加载到内存中进行桶排序,然后将排序后的数据写回磁盘,最终将所有块合并得到有序序列。
  • 计数排序的扩展: 桶排序是计数排序的一种扩展,适用于更大范围的数据。当计数排序不适用于数据范围很大的情况时,桶排序可以更好地处理这种情况。

桶排序的关键点:

  • 确定桶的数量和大小:在进行桶排序之前,需要确定桶的数量和大小。桶的数量可以根据待排序数据的范围来确定,而桶的大小可以根据数据的分布情况来选择。通常情况下,桶的数量可以取数据个数的平方根或者数据范围的大小。
  • 数据分配到桶中:一旦确定了桶的数量和大小,接下来需要将待排序数据分配到对应的桶中。这通常涉及到计算每个数据在哪个桶内,可以根据数据的大小和桶的范围来确定。
  • 对每个非空桶内的数据进行排序:在桶排序中,每个非空桶内的数据需要进行排序。这可以使用任意一种排序算法,如插入排序、快速排序等。在实际应用中,通常选择适合桶内数据规模的排序算法。
  • 合并各个桶的数据:最后一步是将各个桶内的数据按照顺序合并为最终的排序结果。这通常涉及遍历所有桶,按照桶的顺序将数据合并起来。
  • 处理边界情况:在实现桶排序时,需要考虑处理一些边界情况,例如空桶的情况、数据分布不均匀的情况等。这些情况可能会影响桶排序的性能和正确性。

参考链接:
十大经典排序算法(Java实现)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】进程的地址空间

一、看现象 1 #include<stdio.h>2 #include<unistd.h>3 4 int g_val 100;5 int main()6 {7 printf("father process is running!pid: %d,ppid: %d\n",getpid(),getppid( ));8 sleep(1);9 10 int id fork();11 if(id 0)12 {13 int cn…

vue快速入门(三十二)局部与全局注册组件的步骤

注释很详细&#xff0c;直接上代码 上一篇 新增内容 局部注册组件全局注册组件 文件结构 源码 MyHeader.vue <!-- 用于测试全局注册组件 --> <template><div><h1>又可以愉快的学习啦</h1></div> </template><script>export d…

go语言并发实战——日志收集系统(四) 利用tail包实现对日志文件的实时监控

Linux中的tail命令 tail 命令是一个在 Unix/Linux 操作系统上用来显示文件末尾内容的命令。它可以显示文件的最后几行内容&#xff0c;默认情况下显示文件的最后 10 行。tail 命令 非常有用&#xff0c;特别是在我们查看日志文件或者监视文件变化时。 基本用法如下&#xff1a…

LLM生成模型在生物单细胞single cell的应用:scGPT

参考&#xff1a; https://github.com/bowang-lab/scGPT https://www.youtube.com/watch?vXhwYlgEeQAs 相关算法&#xff1a; 主要是把单细胞测序出来的基因表达量的拼接起来构建成的序列&#xff0c;这里不是用的基因的ATCG&#xff0c;是直接用的基因名称 训练数据&#x…

vue+springboot+websocket实时聊天通讯功能

前言 在我的前一篇文章里 vuespringboot实现聊天功能 &#x1f388;&#x1f388;&#x1f388;&#x1f388;&#x1f388;&#x1f388;&#x1f388; 实现了最最基础的聊天功能&#xff0c;可以通过聊天互相给对方发送信息 &#x1f388;&#x1f388;&#x1f388;&…

Python全栈开发前端与后端的完美融合

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在当今互联网时代&#xff0c;全栈开发已经成为了一种趋势。全栈开发者具备前端和后端开发的…

KMP算法(Python)

进阶的做法就是KMP算法&#xff0c;当然暴力也能ac。 KMP主要用一个nex列表&#xff0c;nex[i]存储&#xff08;模式串needle中&#xff09;从第0个到i个字符串s中的一个相等前后缀的最大长度。比如说对于aabaa来说&#xff0c;最大长度应该是&#xff08;前缀aa&#xff09;和…

HarmonyOS开发案例:【首选项】

介绍 本篇Codelab是基于HarmonyOS的首选项能力实现的一个简单示例。实现如下功能&#xff1a; 创建首选项数据文件。将用户输入的水果名称和数量&#xff0c;写入到首选项数据库。读取首选项数据库中的数据。删除首选项数据文件。 最终效果图如下&#xff1a; 相关概念 [首…

盗梦攻击:虚拟现实系统中的沉浸式劫持

虚拟现实&#xff08;VR&#xff09;硬件和软件的最新进展将改变我们与世界和彼此互动的方式&#xff0c;VR头显有可能为用户提供几乎与现实无差别的深度沉浸式体验。它们还可以作为一种跨越遥远距离的方式&#xff0c;通过使用个性化的化身或我们的数字代表&#xff0c;促进社…

笔记-----BFS宽度优先搜索

对于BFS&#xff1a;宽搜第一次搜到就是最小值&#xff0c;并且基于迭代&#xff0c;不会爆栈。 Flood Fill 模型 如果直译的话就是&#xff1a;洪水覆盖&#xff0c;意思就是像是从一个点一圈圈的往外扩散&#xff0c;如果遇见能够连通的就扩散&#xff0c;如果遇见无法联通的…

汽车4S集团数据分析

派可数据分析--汽车4S集团。 派可数据汽车4S集团数据分析概述。派可数据汽车4S集团分析主题全面涵盖行业内各板块业务分析&#xff0c;具体包括&#xff1a;保险业务分析、客户关系分析、汽车保养情况分析、售后维修主题分析、整车销售分析、整车库存分析、装具销售分析、配件…

原型和原型链--图解

https://juejin.cn/post/7255605810453217335 prototype是函数的属性&#xff08;一个对象&#xff09;&#xff0c;不是对象的属性&#xff0c;普通函数和构造函数的prototype属性是空对象&#xff5b;&#xff5d;&#xff08;其实有2个属性&#xff0c;一个是constructor&a…

账号安全基本措施2

sudo命令 sudo(superuser do)&#xff0c;允许系统管理员让普通用户执行一些或者全部的root命令的一个工具。 其配置在/etc/sudoers权。它允许系统管理员集中的管理用户的使用权限和使用的主机。属性必须为0440。 语法检查&#xff1a; 检查语法&#xff1a; 修改文件时&…

Excel中将单元格格式改成文本后,为何要双击数字才会改变?

将大批量的数值型数字转换成文本型数字&#xff0c;当然不能一个一个的去双击做转换了。以下说说有哪个可以将数值型数字转换成文本型数字的方法。 一、转换方法 方法1.数据分列功能 选中数据后&#xff0c;点击数据选项卡&#xff0c;分列&#xff0c; 分列向导的第一步和…

部署轻量级Gitea替代GitLab进行版本控制(一)

Gitea 是一款使用 Golang 编写的可自运营的代码管理工具。 Gitea Official Website gitea: Gitea的首要目标是创建一个极易安装&#xff0c;运行非常快速&#xff0c;安装和使用体验良好的自建 Git 服务。我们采用Go作为后端语言&#xff0c;这使我们只要生成一个可执行程序即…

ACL的基本配置

已经启用&#xff52;&#xff49;&#xff50;实现了全网可达。 这时我们要拒绝R1与R4的路由通信&#xff0c;做标准ACL过滤关注源IP需要尽量靠近目标。则在R4的物理接口G0/0/1的&#xff49;&#xff4e;接口上做&#xff0c;不能在R4的环回接口上做&#xff0c;因为ACL列表…

【Taro3踩坑日记】找不到sass的类型定义文件

问题截图如下&#xff1a;找不到sass的类型定义文件 解决办法&#xff1a; 1、npm i types/sass1.43.1 2、然后配置 TypeScript 编译选项&#xff1a;确保 TypeScript 编译器能够识别 Sass 文件&#xff0c;并正确处理它们。

小程序 前端如何用wx.request获取 access_token接口调用凭据

在微信小程序中&#xff0c;获取access_token通常是通过wx.request方法来实现的。以下是一个简单的示例代码&#xff1a; 1.获取小程序的appID 与 secret&#xff08;小程序密钥&#xff09; 登录之后,请点击左侧的"开发管理">点击"开发设置" 就可以找…

在Ubuntu 22.04上安装配置VNC实现可视化

前面安装的部分可以看我这篇文章 在Ubuntu 18.04上安装配置VNC实现Spinach测试可视化_ubuntu18开vnc-CSDN博客 命令差不多一样&#xff1a; sudo apt update sudo apt install xfce4 xfce4-goodies sudo apt install tightvncserver这个时候就可以启动server了 启动server&…

.net8系列-01手把手教你创建一个新的.net应用(.net7和.net8的不同点)以及三种方案进行接口调试

前提条件 如果没有安装VS2022.17.8 版本环境&#xff0c;请参考我的.net系列其他安装步骤文章来进行安装&#xff08;发布本文的时候另一篇文章正在审核无法放链接&#xff0c;等后续补充哦&#xff0c;也可以自己搜索我的博文哦~很齐全&#xff09; Windows版本.net环境搭建…
最新文章