python排序算法之(基数排序)

本文深入解析基数排序算法,演示其工作原理及实现过程,通过代码示例展示如何对数组进行高效排序,同时讨论了基数排序在大数据量场景下的局限性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

基数排序的基本思想是:
       将所有待比较的数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序,这样从最低位排序一直到最高位排序完成以后,数列就变为了一个有序序列。
这样说可能过于抽象,我们通过详细步骤来分析一下:
我们假设有一个待排序数组[53,3,542,748,14,214],那么如何使用基数排序对其进行排序呢?
首先我们有这样的十个一维数组,在基数排序中也叫桶。

       那么第一轮排序开始,我们依次遍历每个元素,并得到元素的个位数。拿到的第一个元素为53,其个位数为3,所以将53放入编号为3的桶中,第二个元素3的个位数也是3,所以也放在编号为3的桶中,而第三个元素542的个位数为2,所以将542放入编号为2的桶中,以此类推。
所以结果为:

       将元素全部放入桶中之后,我们需要按照桶的顺序(也就是一维数组的下标)依次取出数据,并放回原来的数组。
那么很简单,按顺序取出数据并放回原数组之后,原数组将变为[542,53,3,14,214,748]。
这样第一轮就完成了,接下来开始第二轮。
第二轮排序和第一轮类似,也要去遍历数组元素,但不同的是第二轮的存放顺序取决于十位数。
取出数据的第一个元素为542,十位数为4,所以放入编号为4的桶;第二个元素53,十位数为5,所以放入编号为5的桶;第三个元素3,十位数为0,所以放入编号为0的桶,以此类推。
所以结果为:

        然后同样按照桶的顺序将数据从中取出并放入原数组,此时原数组变为[3,14,214,542,748,53]。
接下来又进行第三轮排序,以元素的百位数进行区分,结果为:

按顺序取出数据后,原数组变为[3,14,53,214,542,748]。这时的数组已经完成排序。
从中我们也可以知道,基数排序的排序轮数取决于数组元素中最大位数的元素。

代码如下:

    public static void raixSort(int[] arr) {
        // 第一轮(针对每个元素的个位进行排序处理)

        // 定义一个二维数组,模拟桶,每个桶就是一个一维数组
        // 为了防止放入数据的时候桶溢出,我们应该尽量将桶的容量设置得大一些
        int[][] bucket = new int[10][arr.length];
        // 记录每个桶中实际存放的元素个数
        // 定义一个一维数组来记录每个桶中每次放入的元素个数
        int[] bucketElementCounts = new int[10];

        for (int j = 0; j < arr.length; j++) {
            // 取出每个元素的个位
            int digitOfElement = arr[j] % 10;
            // 将元素放入对应的桶中
            // bucketElementCounts[digitOfElement]就是桶中的元素个数,初始为0,放在第一位
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            // 将桶中的元素个数++
            // 这样接下来的元素就可以排在前面的元素后面
            bucketElementCounts[digitOfElement]++;
        }
        // 按照桶的顺序取出数据并放回原数组
        int index = 0;
        for (int k = 0; k < bucket.length; k++) {
            // 如果桶中有数据,才取出放回原数组
            if (bucketElementCounts[k] != 0) {
                // 说明桶中有数据,对该桶进行遍历
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    // 取出元素放回原数组
                    arr[index++] = bucket[k][l];
                }
            }
            // 第一轮处理后,需要将每个bucketElementCounts[k]置0
            bucketElementCounts[k] = 0;
        }
        System.out.println("第一轮:" + Arrays.toString(arr));

        // ----------------------------

        // 第二轮(针对每个元素的十位进行排序处理)
        for (int j = 0; j < arr.length; j++) {
            // 取出每个元素的十位
            int digitOfElement = arr[j] / 10 % 10;
            // 将元素放入对应的桶中
            // bucketElementCounts[digitOfElement]就是桶中的元素个数,初始为0,放在第一位
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            // 将桶中的元素个数++
            // 这样接下来的元素就可以排在前面的元素后面
            bucketElementCounts[digitOfElement]++;
        }
        // 按照桶的顺序取出数据并放回原数组
        index = 0;
        for (int k = 0; k < bucket.length; k++) {
            // 如果桶中有数据,才取出放回原数组
            if (bucketElementCounts[k] != 0) {
                // 说明桶中有数据,对该桶进行遍历
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    // 取出元素放回原数组
                    arr[index++] = bucket[k][l];
                }
            }
            // 第二轮处理后,需要将每个bucketElementCounts[k]置0
            bucketElementCounts[k] = 0;
        }
        System.out.println("第二轮:" + Arrays.toString(arr));

        // ----------------------------

        // 第三轮(针对每个元素的百位进行排序处理)
        for (int j = 0; j < arr.length; j++) {
            // 取出每个元素的百位
            int digitOfElement = arr[j] / 100 % 10;
            // 将元素放入对应的桶中
            // bucketElementCounts[digitOfElement]就是桶中的元素个数,初始为0,放在第一位
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            // 将桶中的元素个数++
            // 这样接下来的元素就可以排在前面的元素后面
            bucketElementCounts[digitOfElement]++;
        }
        // 按照桶的顺序取出数据并放回原数组
        index = 0;
        for (int k = 0; k < bucket.length; k++) {
            // 如果桶中有数据,才取出放回原数组
            if (bucketElementCounts[k] != 0) {
                // 说明桶中有数据,对该桶进行遍历
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    // 取出元素放回原数组
                    arr[index++] = bucket[k][l];
                }
            }
            // 第三轮处理后,需要将每个bucketElementCounts[k]置0
            bucketElementCounts[k] = 0;
        }
        System.out.println("第三轮:" + Arrays.toString(arr));
    }

为了方便大家理解,这里我并没有使用循环处理,而是详细写出了每一轮的步骤,代码注释也很详细。
接下来编写测试代码:

    public static void main(String[] args) {
        int[] arr = { 53, 3, 542, 748, 14, 214 };
        raixSort(arr);
    }

运行结果:

第一轮:[542, 53, 3, 14, 214, 748]
第二轮:[3, 14, 214, 542, 748, 53]
第三轮:[3, 14, 53, 214, 542, 748]

如果你能够看懂上面的代码,那么接下来就是整合了,通过循环对上面的代码进行优化。
代码如下:

    public static void raixSort(int[] arr) {
        // 得到数组中最大的数
        int max = arr[0];// 假设第一个数就是数组中的最大数
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        // 得到最大数是几位数
        // 通过拼接一个空串将其变为字符串进而求得字符串的长度,即为位数
        int maxLength = (max + "").length();

        // 定义一个二维数组,模拟桶,每个桶就是一个一维数组
        // 为了防止放入数据的时候桶溢出,我们应该尽量将桶的容量设置得大一些
        int[][] bucket = new int[10][arr.length];
        // 记录每个桶中实际存放的元素个数
        // 定义一个一维数组来记录每个桶中每次放入的元素个数
        int[] bucketElementCounts = new int[10];

        // 通过变量n帮助取出元素位数上的数
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            for (int j = 0; j < arr.length; j++) {
                // 针对每个元素的位数进行处理
                int digitOfElement = arr[j] / n % 10;
                // 将元素放入对应的桶中
                // bucketElementCounts[digitOfElement]就是桶中的元素个数,初始为0,放在第一位
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                // 将桶中的元素个数++
                // 这样接下来的元素就可以排在前面的元素后面
                bucketElementCounts[digitOfElement]++;
            }
            // 按照桶的顺序取出数据并放回原数组
            int index = 0;
            for (int k = 0; k < bucket.length; k++) {
                // 如果桶中有数据,才取出放回原数组
                if (bucketElementCounts[k] != 0) {
                    // 说明桶中有数据,对该桶进行遍历
                    for (int l = 0; l < bucketElementCounts[k]; l++) {
                        // 取出元素放回原数组
                        arr[index++] = bucket[k][l];
                    }
                }
                // 每轮处理后,需要将每个bucketElementCounts[k]置0
                bucketElementCounts[k] = 0;
            }
        }
    }


测试代码:

    public static void main(String[] args) {
        int[] arr = { 53, 3, 542, 748, 14, 214 };
        raixSort(arr);
        System.out.println(Arrays.toString(arr));
    }

运行结果:

[3, 14, 53, 214, 542, 748]
1
这样,基数排序就完成了。大家不要看到代码很多就怕了、烦了,觉得好难,其实也不能说一点难度都没有吧,只是要去理解这个过程,所以对于排序过程的分析我写了很多,也是为了能让你们更加理解,掌握了过程之后,相信理解这些代码也不是难事了。
其它:
这里说一说基数排序的一些其它内容,为什么单独只说基数排序呢?我们在前面提到了,基数排序是用空间换时间的经典算法,所以基数排序对于元素排序是非常快的。不信我们可以测试一下(先测试八十万个数据的排序时间):

    public static void main(String[] args) {
        int[] arr = new int[800000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 8000000);
        }
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
        String dateStr = format.format(date);
        System.out.println("排序前的时间是:" + dateStr);
        raixSort(arr);
        Date date2 = new Date();
        String dateStr2 = format.format(date2);
        System.out.println("排序前的时间是:" + dateStr2);
    }

运行结果:

排序前的时间是:17:37:21
排序前的时间是:17:37:21
1
2
一秒钟时间不到就完成排序了。
我们再测试一下八百万个数据的排序:

    public static void main(String[] args) {
        int[] arr = new int[8000000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 8000000);
        }
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
        String dateStr = format.format(date);
        System.out.println("排序前的时间是:" + dateStr);
        raixSort(arr);
        Date date2 = new Date();
        String dateStr2 = format.format(date2);
        System.out.println("排序前的时间是:" + dateStr2);
    }

运行结果:

排序前的时间是:17:38:04
排序前的时间是:17:38:05
1
2
只需要一秒钟即完成了排序。
我们再测试一下八千万个数据的排序:

    public static void main(String[] args) {
        int[] arr = new int[80000000];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 8000000);
        }
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
        String dateStr = format.format(date);
        System.out.println("排序前的时间是:" + dateStr);
        raixSort(arr);
        Date date2 = new Date();
        String dateStr2 = format.format(date2);
        System.out.println("排序前的时间是:" + dateStr2);
    }

运行结果:

排序前的时间是:17:41:07
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.itcast.sort.RadixSortDemo.raixSort(RadixSortDemo.java:42)
    at com.itcast.sort.RadixSortDemo.main(RadixSortDemo.java:22)

结果为堆内存溢出,所以在对大量数据进行排序的时候,基数排序显然不是一个好的选择,因为提升排序效率的条件是牺牲大量的内存空间,当数据足够多时,内存空间就不够用了。
 ———————————————— 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值