桶排序 计数排序 基数排序的实现

基数排序

  1. 基数排序核心思想是将一个排序规则划分为多个关键字, 根据关键字依次采用稳定性排序.
  2. 基数排序不属于"分配类排序"(桶排序是一种"分配类排序"), 只是借用了"分配类排序"这种稳定性排序来实现根据关键字排序, 也可以将基数排序根据关键字的排序算法改成其它稳定性排序

分析

底层思想

  1. 基数排序是将排序规则划分为多个关键字. 先按照低权重的关键字进行排序,然后在之前排序基础上按照高权重的关键字排序.
  2. 只要每次排序时采用稳定性排序方式,就可以得到最终结果. 原理是采用稳定性排序后, 对之前低权重的排序结果并不会干扰.

时间复杂度

  1. 基数排序时间复杂度: 因为k(关键字数量)是常数可以看作是O(n).
  2. 如果采用链式存储结构的时间/空间复杂度变化和计数排序的链式结构很很像,暂不分析.

空间复杂度

稳定性

  1. 因为基数排序每一轮是根据关键字进行’分配-收集’排序, 这个’分配-收集’过程并不会对值相同的元素进行任何交换.
  2. 而之后再根据高权重的关键字进行分配-收集排序时是按照上次排序后的结果顺序遍历进行, 所以和上一次相同不会对值相同的元素进行任何交换. 所以没有打破稳定性的操作可能

代码实现

public class RadixSort extends Sort {
    public static void main(String[] args) {
        RadixSort radixSort = new RadixSort();

        radixSort.correctTest(new int[]{11, 1233, 42, 123, 543, 42, 0, 42, 98, 65, 9, 8, 9});
    }

    @Override
    public void sortNums(int[] nums) {
        radixSort(nums);
    }

    /**
     * 当前实现的基数排序只能排序正整数
     */
    public void radixSort(int[] nums) {
        // 1.准备桶
        int[][] buckets = new int[10][nums.length];

        // 根据关键字,n轮分配-收集
        for (int i = 0; i < lengthOfMaxNum(nums); i++) {
            // 每个桶数组的指针,用于存/取元素
            int[] bucketSize = new int[10];

            // 2.分配
            for (int j = 0; j < nums.length; j++) {
                // 获得每轮排序所依据的位
                int whichBucket = getNumInDecimal(nums[j], i + 1);
                // 放到桶数组中
                int bucketIndex = bucketSize[whichBucket];
                buckets[whichBucket][bucketIndex] = nums[j];
                bucketSize[whichBucket]++;
            }

            // 3.收集
            int numsIndex = 0;
            for (int j = 0; j < buckets.length; j++) {
                for (int k = 0; k < bucketSize[j]; k++) {
                    nums[numsIndex++] = buckets[j][k];
                }
            }
        }
    }

    public int lengthOfMaxNum(int[] nums) {
        int maxLen = 0;
        for (int i = 0; i < nums.length; i++) {
            int len = getLengthOfNum(nums[i]);
            maxLen = Math.max(maxLen, len);
        }
        return maxLen;
    }

    /**
     * 获取一个整数(可正可负)的位数
     */
    public int getLengthOfNum(int num) {
        int len = 0;
        while (num != 0) {
            len++;
            num /= 10;
        }
        return len;
    }

    /**
     * 获取10进制下的每一位
     * @param lastIndex 倒数第i位(i从1开始)
     */
    public int getNumInDecimal(int num, int lastIndex) {
        int div = 1;
        for (int i = 0; i < lastIndex - 1; i++) {
            div *= 10;
        }
        int res = num / div % 10;
        return res;
    }
}

补充

  1. 数据结构与算法 C语言版中提到主要用链表来进行基数排序,用链表确实可以减少额外空间.

桶排序

分析

  1. 也是非比较(分配收集)排序
  2. 只要在分配和收集过程保证稳定性, 对每个桶的排序采用稳定性排序就可以保证整个桶排序是稳定的

代码实现

public class BucketSort extends Sort {
    public static void main(String[] args) {
        BucketSort bucketSort = new BucketSort();
        bucketSort.correctTest(new int[]{5,4,3,2,1,0});
    }

    @Override
    public void sortNums(int[] nums) {
        bucketSort(nums);
    }

    public void bucketSort(int[] nums) {
        // 1. 分桶策略可以写成根据nums自适应,也可以写死
        int bucketsSize = 5;
        int[][] buckets = new int[bucketsSize][nums.length];
        // 记录每个桶对应数组的指针,用于向该桶放入数据
        int[] bucketSize = new int[bucketsSize];

        // 2. 每个桶接收数据范围
        int[] minMax = getMinMax(nums);
        int min = minMax[0];
        int max = minMax[1];
        // 我采用固定桶数的策略,这里是确定gap,需要考虑仔细,比如[0,1,2,3,4,5]分5组的情况
        int gap = (int)Math.ceil((max - min + 1.0) / (bucketsSize));

        // 3.分配
        for (int i = 0; i < nums.length; i++) {
            // 寻找桶下标
            int whichBucket = (nums[i] - min) / gap;
            // 放入到该桶的数组中
            int bucketIndex = bucketSize[whichBucket];
            buckets[whichBucket][bucketIndex] = nums[i];
            // 将该桶数组指针+1
            bucketSize[whichBucket]++;
        }

        // 4.对每个桶进行排序 -- 排序算法任意选择(注意该排序算法的稳定性影响整个算法的稳定性)
        for (int i = 0; i < bucketsSize; i++) {
            MergeSort mergeSort = new MergeSort();
            if (bucketSize[i] != 0) {
                mergeSort.mergeSort(buckets[i], 0, bucketSize[i] - 1);
            }
        }

        // 5.收集
        int numsIndex = 0;
        for (int i = 0; i < bucketsSize; i++) {
            for (int j = 0; j < bucketSize[i]; j++) {
                nums[numsIndex++] = buckets[i][j];
            }
        }
    }

    public int[] getMinMax(int[] nums) {
        int min = nums[0];
        int max = nums[0];
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] < min) {
                min = nums[i];
            }
            if (nums[i] > max) {
                max = nums[i];
            }
        }
        return new int[]{min, max};
    }
}

计数排序

分析

  1. 作为分配类(非比较)排序的一种. 适用场景很有限, 几乎只能用在可以转换为"小范围数字模型"的场景中
  2. 虽然下面对正数进行排序无法体现稳定性, 但是在需要实现稳定排序的场景中, 计数排序可在分配和收集过程中做到稳定性, 所以计数排序是稳定性的

代码实现

public class CountSort extends Sort {
    public static void main(String[] args) {
        CountSort countSort = new CountSort();
        countSort.correctTest(null);
    }

    @Override
    public void sortNums(int[] nums) {
        countSort(nums);
    }

    public void countSort(int[] nums) {
        // 1.寻找nums中最小值/最大值,确定所需桶的个数
        int[] minMax = getMinMax(nums);
        int min = minMax[0];
        int max = minMax[1];
        int[] buckets = new int[max - min + 1];

        // 2.分配
        for (int i = 0; i < nums.length; i++) {
            // 通过num-min找到桶下标
            int bucketIndex = nums[i] - min;
            buckets[bucketIndex]++;
        }

        // 3.收集
        int numsIndex = 0;
        for (int i = 0; i < buckets.length; i++) {
            // 收集的数字就是桶下标对应的num
            int num = i + min;
            // 桶子装了多少个num就在nums中添加多少个num
            for (int j = 0; j < buckets[i]; j++) {
                nums[numsIndex++] = num;
            }
        }
    }

    public int[] getMinMax(int[] nums) {
        int min = nums[0];
        int max = nums[0];
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] < min) {
                min = nums[i];
            }
            if (nums[i] > max) {
                max = nums[i];
            }
        }
        return new int[]{min, max};
    }
}

补充说明

  1. Sort类在博主排序专栏的文章中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值