【一起学数据结构与算法】计数排序、基数排序、桶排序(含菜鸟教程代码)

news2024/10/5 18:30:44

目录

  • 前言
  • 一、计数排序
    • 1.1 排序思想
    • 1.2 代码
    • 1.3 菜鸟教程官方代码(搬运)
  • 二、基数排序
    • 2.1 排序思想
    • 2.2 代码
    • 2.3 菜鸟教程官方代码(搬运)
  • 三、桶排序
    • 3.1 排序思想
    • 3.2 代码
    • 3.3 菜鸟教程官方代码(搬运)

前言

之前我们学过了几种常见的排序,都是基于比较的排序,我们也知道其中平均时间复杂度都是O(n * logn),要达到线性的时间复杂度,我们就需要新的排序方法!
接下来为大家介绍的三种排序方法都是不基于比较的排序!

一、计数排序

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 [1] 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n * log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n * log(n)), 如归并排序,堆排序)

1.1 排序思想

计数的排序,对于每一个待排序的元素,如果知道待排序数组中有多少个比这个元素小的数,我们就可以直接知道在排序后的数组中该元素在什么位置上。
这就是计数排序的思想,是不是很简单呀?可是实现起来就有点难了!

步骤:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入用来计数的数组C的第i项
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

请添加图片描述

1.2 代码

	/**
     * 计数排序
     * 一般适用于有n个数,数据范围是0-n之间
     * @param array
     */
    public static void countSort(int[] array) {
        int maxVal = array[0];
        int minVal = 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];
        // 统计array数组当中,每个数据出现的次数
        for (int i = 0; i < array.length; i++) {
            int index = array[i];
            count[index - minVal]++;
        }
        // 接下来,只需要,遍历计数数组,把数据写回array
        int indexArray = 0;
        for (int i = 0; i < count.length; i++) {
            while (count[i] > 0) {
                // 这里一定要加minVal,因为不一定就是i出现了count[i]
                array[indexArray] = i + minVal;
                count[i]--;// 拷贝一个之后,次数也就少一个
                indexArray++;// 下标得向后移动
            }
        }
    }

1.3 菜鸟教程官方代码(搬运)

public class CountingSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        int maxValue = getMaxValue(arr);

        return countingSort(arr, maxValue);
    }

    private int[] countingSort(int[] arr, int maxValue) {
        int bucketLen = maxValue + 1;
        int[] bucket = new int[bucketLen];

        for (int value : arr) {
            bucket[value]++;
        }

        int sortedIndex = 0;
        for (int j = 0; j < bucketLen; j++) {
            while (bucket[j] > 0) {
                arr[sortedIndex++] = j;
                bucket[j]--;
            }
        }
        return arr;
    }

    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }

}

二、基数排序

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r )m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

2.1 排序思想

基数排序整体来看是一种“分配和收集”排序,其将序列中的元素按照位数进行划分,不够的补0,将相同位数的元素先分配,分配后相同位数的元素排好序后,进行收集,重复该操作,可以完成排序。也可以这样理解,基数排序使用的是一种分治思想,对序列中相同位数的元素进行计数排序,所有的位数循环完成,则排序完成。

  1. 获取序列中的最大值,获取其长度length
  2. 新建一个二维数组用于模拟桶存放数据,取临时变量temp=1
  3. 循环的次数为length,新建数组count,用于保存各个桶中元素的个数,遍历原序列,序列中的元素先除以temp,再对10取余则可以得到,元素中个位数的值,将其放入各自编号的桶中,遍历结束,遍历count将排完序的数据放入到原数组中正确的位置,以此类推获取百位数,千位数,直到length循环结束。

请添加图片描述

2.2 代码

public static void radixSort(int[] array) {
        if (array == null || array.length == 0) {
            return;
        }

        // 获取最大值
        int maxVal = array[0];
        for (int i = 0; i < array.length; i++) {
            if (array[i] > maxVal) {
                maxVal = array[i];
            }
        }

        // 用二维数组模拟桶放入东西,一共10个桶
        int[][] tmp = new int[10][array.length];

        // 元素最大值的长度
        String maxString = maxVal + "";
        int len = maxString.length();
        int tmp1 = 1;
        for (int i = 0; i < len; i++) {
            // 用于记录每个桶中放入元素的位置
            int[] count = new int[10];
            for (int j = 0; j < array.length; j++) {
                int a = (array[j] / tmp1) % 10;
                tmp[a][count[a]] = array[j];
                count[a]++;
            }
            tmp1 *= 10;
            // 将数据取出放入原数组
            int ret = 0;
            for (int j = 0; j < count.length; j++) {
                for (int k = 0; k < count[j]; k++) {
                    array[ret++] = tmp[i][j];
                }
            }
        }
    }

2.3 菜鸟教程官方代码(搬运)

/**
 * 基数排序
 * 考虑负数的情况还可以参考: https://code.i-harness.com/zh-CN/q/e98fa9
 */
public class RadixSort implements IArraySort {

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        int maxDigit = getMaxDigit(arr);
        return radixSort(arr, maxDigit);
    }

    /**
     * 获取最高位数
     */
    private int getMaxDigit(int[] arr) {
        int maxValue = getMaxValue(arr);
        return getNumLenght(maxValue);
    }

    private int getMaxValue(int[] arr) {
        int maxValue = arr[0];
        for (int value : arr) {
            if (maxValue < value) {
                maxValue = value;
            }
        }
        return maxValue;
    }

    protected int getNumLenght(long num) {
        if (num == 0) {
            return 1;
        }
        int lenght = 0;
        for (long temp = num; temp != 0; temp /= 10) {
            lenght++;
        }
        return lenght;
    }

    private int[] radixSort(int[] arr, int maxDigit) {
        int mod = 10;
        int dev = 1;

        for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
            // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
            int[][] counter = new int[mod * 2][0];

            for (int j = 0; j < arr.length; j++) {
                int bucket = ((arr[j] % mod) / dev) + mod;
                counter[bucket] = arrayAppend(counter[bucket], arr[j]);
            }

            int pos = 0;
            for (int[] bucket : counter) {
                for (int value : bucket) {
                    arr[pos++] = value;
                }
            }
        }

        return arr;
    }

    /**
     * 自动扩容,并保存数据
     *
     * @param arr
     * @param value
     */
    private int[] arrayAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }
}

三、桶排序

桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

3.1 排序思想

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

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

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

3.2 代码

public static void bucketSort(int[] array) {
        ArrayList bucket[] = new ArrayList[5]; // 声明5个桶
        for (int i = 0; i < bucket.length; i++) {
            bucket[i] = new ArrayList<Integer>(); // 确定桶的格式为ArrayList
        }
        for (int i = 0; i < array.length; i++) {
            int index = array[i] / 10; // 确定元素存放的桶号
            bucket[index].add(array[i]); // 将元素存入对应的桶中
        }
        for (int i = 0; i < bucket.length; i++) {
            bucket[i].sort(null); // 对每一个桶排序
            for (int j = 0; j < bucket[i].size(); j++) { // 遍历桶中的元素并输出
                System.out.println(bucket[i].get(j) + " ");
            }
        }
    }

3.3 菜鸟教程官方代码(搬运)

public class BucketSort implements IArraySort {

    private static final InsertSort insertSort = new InsertSort();

    @Override
    public int[] sort(int[] sourceArray) throws Exception {
        // 对 arr 进行拷贝,不改变参数内容
        int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

        return bucketSort(arr, 5);
    }

    private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
        if (arr.length == 0) {
            return arr;
        }

        int minValue = arr[0];
        int maxValue = arr[0];
        for (int value : arr) {
            if (value < minValue) {
                minValue = value;
            } else if (value > maxValue) {
                maxValue = value;
            }
        }

        int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
        int[][] buckets = new int[bucketCount][0];

        // 利用映射函数将数据分配到各个桶中
        for (int i = 0; i < arr.length; i++) {
            int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
            buckets[index] = arrAppend(buckets[index], arr[i]);
        }

        int arrIndex = 0;
        for (int[] bucket : buckets) {
            if (bucket.length <= 0) {
                continue;
            }
            // 对每个桶进行排序,这里使用了插入排序
            bucket = insertSort.sort(bucket);
            for (int value : bucket) {
                arr[arrIndex++] = value;
            }
        }

        return arr;
    }

    /**
     * 自动扩容,并保存数据
     *
     * @param arr
     * @param value
     */
    private int[] arrAppend(int[] arr, int value) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        arr[arr.length - 1] = value;
        return arr;
    }

}

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

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

相关文章

ES6中扩展对象的功能性

对象是JavaScript编程的核心&#xff0c;ECMAScript6为对象提供了许多简单易用且更加灵活的新特性。 ECMAScript 6在对象字面量的基础上做出了以下几个变更&#xff1a; 简化属性定义语法&#xff0c;使将当前作用域中的同名变量赋值给对象的语法更加简洁 function createPe…

【MySQL】MySQL基本操作详解

系列文章目录 第1篇&#xff1a;【MySQL】MySQL介绍及安装 第2篇&#xff1a;【MySQL】MySQL基本操作详解 文章目录 ✍1&#xff0c;数据库操作     &#x1f50d;1.1,查看数据库     &#x1f50d;1.2,创建数据库     &#x1f50d;1.3,选择数据库     &…

Kubernetes基础_02_Pod全解析

系列文章目录 文章目录系列文章目录前言一、Pod的生命周期Lifecycle二、Pod的重启策略RestartPolicy三、静态Pod四、Pod的健康检查总结前言 Pod是Kubernetes最小单位&#xff0c;当然一个Pod可以有多个Container&#xff0c;但是container是docker的元素&#xff0c;不是Kuber…

CTFHub | 布尔盲注

0x00 前言 CTFHub 专注网络安全、信息安全、白帽子技术的在线学习&#xff0c;实训平台。提供优质的赛事及学习服务&#xff0c;拥有完善的题目环境及配套 writeup &#xff0c;降低 CTF 学习入门门槛&#xff0c;快速帮助选手成长&#xff0c;跟随主流比赛潮流。 0x01 题目描述…

无限题库公众号系统搭建

无限题库公众号系统搭建 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;…

《设计模式:可复用面向对象软件的基础》——行为模式(笔记)

文章目录五、行为模式5.1 CHAIN OF RESPONSIBILITY(职责链)1.意图2.动机3.适用性4.结构5.参与者6.协作7.效果8.实现9.代码示例10.相关模式5.2 COMMAND&#xff08;命令&#xff09;1.意图2.别名3.动机4.适用性5.结构6.参与者.7.协作8.效果9.实现10.代码示例11.相关模式5.3 INTE…

CSDN独家 | 全网首发 | Pytorch深度学习·理论篇(2023版)目录

很高兴和大家在这里分享我的最新专栏 Pytorch深度学习理论篇(2023版)&#xff0c;恭喜本博客浏览量达到两百万&#xff0c;CSDN内容合伙人&#xff0c;CSDN人工智能领域实力新星~ 0 Pytorch深度学习理论篇实战篇(2023版)大纲 1 Pytorch深度学习理论篇实战篇(2023版)专栏地址&…

嵌入式应用-详解移植并使用freetype显示文字

目录 前言 1. freetype和相关概念简介 2.freetype显示文字流程和主要函数 2.1 包含头文件及API头文件&#xff1a;ft2build.h 2.2 初始化&#xff1a; FT_InitFreetype 2.3 加载&#xff08;打开&#xff09;字体Face&#xff1a; FT_New_Face 2.4 设置字体大小&#x…

sqrt函数模拟实现的两种方法

起因:在leetcode刷题时&#xff0c;有一道题目考察了有关sqrt的原理的题目&#xff0c;当时就去查了网上的文章&#xff0c;结果发现&#xff0c;一开始的时候看的很懵&#xff0c;最后也是搞定了两种方法&#xff0c;今天我就以最简单的方式写下这两种方式的思路讲解&#xff…

Python批量获取高校基本信息

文章目录前言一、需求二、分析三、处理四、运行效果前言 为了更好的掌握数据处理的能力&#xff0c;因而开启Python网络爬虫系列小项目文章。 小项目小需求驱动&#xff0c;每篇文章会使用两种以上的方式&#xff08;Xpath、Bs4、PyQuery、正则等&#xff09;获取想要的数据。…

T31快启图像效果优化

T31快启图像效果优化 liwen01 20220821 (一)基础方法及概念 参考文档 《Ingenic_Zeratul_T31_快起效果调试说明_20200927_CN》 (1)起始EV参数 IPC 每次启动都是冷启动&#xff0c;画面有一个暗变亮的过程&#xff0c;称作为AE收敛过程(自动曝光收敛过程)。 为了加快AE收…

LeetCode50天刷题计划第二季(Day 23 — 重排链表(16.20- 17.00)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、题目重排链表示例提示二、思路三、代码前言 链表基础题加一 一、题目 重排链表 给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff…

基于粒子群优化算法的时间调制非线性频偏FDA(Matlab代码实现)

&#x1f389;&#x1f389;&#x1f389;&#x1f389;欢迎您的到来&#x1f60a;&#x1f60a;&#x1f60a; &#x1f96c;博客主页&#xff1a;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 &#x1f4dd;床头铭&#xff1a;将来的我一定会感谢…

ARM64下构建 UEFI 模块

构建Application工程目录 mkdir edk2/ShellPkg/Application/ShellHello 目录内容如下: 编写 ShellHello.c /* edk2/ShellPkg/Application/ShellHello/ShellHello.c*/ #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/DebugLib.h> #i…

上手之Python之异常

什么是异常 当检测到一个错误时&#xff0c;Python解释器就无法继续执行了&#xff0c;反而出现了一些错误的提示&#xff0c;这就是所谓的“异常”, 也就是我们常说的BUG bug单词的诞生 早期计算机采用大量继电器工作&#xff0c;马克二型计算机就是这样的。 1945年9月9日&…

【JVM技术专题】 深入学习JIT编译器实现机制「 原理篇」

前提概要 解释器 Java程序最初是通过解释器(Interpreter)进行解释执行的&#xff0c;当虚拟机发现某个方法或代码块的运行特别频繁的时候&#xff0c;就会把这些代码认定为“热点代码”&#xff08;hotspot code&#xff09;。正因为如此&#xff0c;我们的hotspot的虚拟机就是…

将matlab数据导入到Python中使用

相信不少小伙伴都遇到过和我一样的问题&#xff0c;就是在尝试使用scipy.io.loadmat将matlab类型的数据导入python中的时候遇到如下错误提示。 import scipy as sc# 设定需要导入的matlab数据目录 path_TE00 r"D:\Window自带文件夹\桌面\PIC Design\Lumerical\Tutorials…

Apollo GraphQL

一、Apollo GraphQL介绍 Apollo 是一个开源的 GraphQL 开发平台&#xff0c; 提供了符合 GraphQL 规范的服务端和客户端实现。使用 Apollo 可以帮助我们更方便快捷的开发使用 GraphQL。 ● 官网&#xff1a;https://www.apollographql.com/ ● GitHub 相关开源仓库&#xff1a…

Vite+Vue+Electron环境搭建

因为electron可以直接加载html文件&#xff0c;也可以直接加载url链接&#xff0c;所以&#xff0c;我们可以在调试过程中使用url地址&#xff0c;来动态显示我们的改变过程。 electron简单来说就是对html的一种封装&#xff0c;所以我们先来搭建vue的开发环境&#xff0c;这里…

LQ0135 左孩子右兄弟【DFS+二叉树】

题目来源&#xff1a;蓝桥杯2021初赛 C A组H题 题目描述 对于一棵多叉树&#xff0c;我们可以通过“左孩子右兄弟” 表示法&#xff0c;将其转化成一棵二叉树。 如果我们认为每个结点的子结点是无序的&#xff0c;那么得到的二叉树可能不唯一。 换句话说&#xff0c;每个结点可…