• (十一)Java算法:计数排序(详细图解)


    一、前言

    1.1、概念

       计数排序:核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数

    1.2、算法步骤

      我们大概讲一下算法的步骤。

    1. 找出待排序的数组中的最大元素max和最小元素min
    2. 统计数组中每个元素num出现的次数,存入数组countArraycountArray[num-min]项中
    3. 计数数组变形,对所有的计数累加(从第一项开始countArray[i] = countArray[i] + countArray[i - 1]
    4. 反向填充目标数组arr:从后往前遍历待排序的数列copyArray(拷贝份),由数组元素num计算出对应的计数数组的索引countIndexnum - min,从而推断出numarr的位置为indexcountArray[num-min] - 1,然后num填充到arr[index],最后记得计数数组的值减1,即countArray[countIndex]–

    二、maven依赖

    pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
            <version>2.6.0version>
        dependency>
    
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.16.14version>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、流程解析

      假设我们要排序的数据是:8, 10, 12, 9, 8, 12, 8

    3.1、计数流程图

      首先我们看看计数统计是什么一回事,计数统计就是把数组中每个元素出现的次数都记录下来,并且能够通过元素找到对应的次数。
    在这里插入图片描述

    3.2、计数数组变形

      那么计数数组变形又是干啥呢?计数数组变形是从第一项开始,每一项都等于它本身和前一项的和,这样做,得到的值的意思是当前值前面还有多个数字,比如arr[1]=4,表示当前值前面有4-1=3个数字;arr[2]=5,表示当前值前面有5-1=4个数字。
    在这里插入图片描述

    3.3、排序过程

      最后我们看下具体是怎么排序的,又我们的数组的值推导得到索引,然后从计数数组中找到应该要排的位置,最后插入到对应的数组中,这种方式也是一种稳定的排序方式。
    在这里插入图片描述

    四、编码实现

      具体的编码实现如下,下面的实现方式也是稳定排序的方式。

    /**
     * 计数排序
     *
     * @param arr
     * @return
     */
    public static void countingSort(int[] arr) {
        if (arr.length == 0) {
            return;
        }
        // 原数组拷贝一份
        int[] copyArray = Arrays.copyOf(arr, arr.length);
        // 初始化最大最小值
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        // 找出最小值和最大值
        for (int num : copyArray) {
            max = Math.max(max, num);
            min = Math.min(min, num);
        }
    
        // 新开辟一个数组用于统计每个元素的个数(范围是:最大数-最小数+1)
        int[] countArray = new int[max - min + 1];
        // 增强for循环遍历
        for (int num : copyArray) {
            // 加上最小偏差是为了让最小值索引从0开始,同时可有节省空间,每出现一次数据就加1
            // 真实值+偏差=索引值
            countArray[num - min]++;
        }
        log.info("countArray的初始值:{}", countArray);
    
        // 获取数组的长度
        int length = countArray.length;
        // 计数数组变形,新元素的值是前面元素累加之和的值
        for (int i = 1; i < length; i++) {
            countArray[i] = countArray[i] + countArray[i - 1];
        }
        log.info("countArray变形后的值:{}", countArray);
    
        // 遍历拷贝数组中的元素,填充到原数组中去,从后往前遍历
        for (int j = copyArray.length - 1; j >= 0; j--) {
            // 数据对应计数数组的索引
            int countIndex = copyArray[j] - min;
            // 数组的索引获取(获取到的计数数组的值n就是表示当前数据前有n-1个数据,数组从0开始,故当前元素的索引就是n-1)
            int index = countArray[countIndex] - 1;
            // 数组中的值直接赋值给原数组
            arr[index] = copyArray[j];
            // 计数数组中,对应的统计值减1
            countArray[countIndex]--;
            log.info("countArray操作后的值:{}", countArray);
        }
        log.info("排列结果的值:{}", arr);
    
    }
    
    public static void main(String[] args) {
        int[] arr = new int[]{8, 10, 12, 9, 8, 12, 8};
        log.info("要排序的初始化数据:{}", arr);
        //从小到大排序
        countingSort(arr);
        log.info("最后排序后的结果:{}", arr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    运行结果:

    要排序的初始化数据:[8, 10, 12, 9, 8, 12, 8]
    countArray的初始值:[3, 1, 1, 0, 2]
    countArray变形后的值:[3, 4, 5, 5, 7]
    countArray操作后的值:[2, 4, 5, 5, 7]
    countArray操作后的值:[2, 4, 5, 5, 6]
    countArray操作后的值:[1, 4, 5, 5, 6]
    countArray操作后的值:[1, 3, 5, 5, 6]
    countArray操作后的值:[1, 3, 5, 5, 5]
    countArray操作后的值:[1, 3, 4, 5, 5]
    countArray操作后的值:[0, 3, 4, 5, 5]
    排列结果的值:[8, 8, 8, 9, 10, 12, 12]
    最后排序后的结果:[8, 8, 8, 9, 10, 12, 12]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    动态RDLC报表(三)
    操作系统学习笔记
    c++ deque 的使用
    python开发工程师面试准备
    HALCON联合C#机械手视觉定位——界面代码
    ESP8266教程5 — MCU和机智云APP之间互相通信
    【1day】复现宏景OA KhFieldTree接口 SQL注入漏洞
    『忘了再学』Shell基础 — 8、管道符介绍
    karmada介绍和分析
    微服务配置配置中心和链路追踪
  • 原文地址:https://blog.csdn.net/Alian_1223/article/details/121206679