桶排序 -- 计数排序

桶排序是不基于比较的排序,是一个大的概念.其 实现 有计数排序和基数排序.

桶排序受数据状况本身影响特别严重.

计数排序

如果知道一个待排序数组中的值是0~60,桶排序的步骤如下

1-建立一个可以包含范围内所有数据的数组,即长度为61的数组arr

2-遍历待排序数组,遍历得到的每个值都在新建的数组对应index上加一.比如第一个值是2,则新建数组arr[2] ++

3-遍历结束后,新建数组中就有了待排序数组的词频统计.根据词频统计从小到大恢复数组即可.

比如现在arr中的值是[3,5,1],代表的就是0有3个,1有5个,2有1个.

计数排序时间复杂度是O(N).额外空间复杂度O(N)

应用场景

给定一个数组,求排序之后,相邻两数的最大差值,要求时间复杂度O(N),且要求不能用非基于比较的排序。

解题思路:

假设给定的数组arr长度为 arr.length = 5,我们需要找到这5个数的最小值和最大值,将这两个值的区间分成 arr.length + 1 = 6份(桶),每一份对应一个数值区间.然后将arr中的5个按照自己对应的区间存到对应的桶中.

细节1 - 因为是按区间存入而且这5个数的大小分布我们不清楚,所以会存在一个区间有多个值的情况.此时我们需要建立两个数组mins和maxs用来存储每个桶区间内的最大值和最小值,忽略中间值.这样节省空间.

细节2- 因为区间的划分是根据arr元素的最小值和最大值决定的,所以arr的最小值一定在第一个桶,最大值一定在最后一个桶,又因为桶数大于arr的元素数,所以肯定存在空桶也就是这个区间内没有数值而且空桶不会出现在开头区间和结尾区间,一定在中间.这样就保证了最大差值不会出现在同一个区间的最大值和最小值上.

细节3 - 按照如上步骤做好以后,用前一个桶的最大值减去后一个桶的最小值,差值最大的就是最终结果.

网上给的左神的源码,获得在第几个桶的部分是错误的.(num - min) * 桶的数量len + 1 而不是(num - min) * len

public class MaxGap {
    public static Integer maxGap(int [] arr){
        int minimun = arr[0];
        int largest = arr[0];
        for (int i = 0; i < arr.length ; i ++){
            minimun = Math.min(arr[i],minimun);
            largest = Math.max(arr[i],largest);
        }
        //最小值等于最大值,不存在差值.
        if (minimun == largest) {
            return 0;
        }

        int len = arr.length;
        int [] min = new int[len + 1];
        int [] max = new int[len + 1];
        boolean [] hasNum = new boolean[len + 1];

        for (int i = 0; i < len ; i++){
            int bucketNum = getBucketNum(arr[i],minimun,largest,len);
            if (!hasNum[bucketNum]){
                min[bucketNum] = arr[i];
                max[bucketNum] = arr[i];
                hasNum[bucketNum] = true;
            }else {
                min[bucketNum] = Math.min(min[bucketNum],arr[i]);
                max[bucketNum] = Math.max(max[bucketNum],arr[i]);
            }
        }

        //用后边桶的最小值 - 前边桶的最大值 = gap
        //用hasNum来确认取最大值的桶是不是要变化,如果后变桶是空的,就不需要变化.
        int res = 0;
        int lastMax = max[0];
        //循环从1开始,1的最小减去0的最大,每次循环更新最大值的坐标.
        for (int i = 1;i < min.length;i++){
            if (hasNum[i]){
                res = Math.max(min[i] - lastMax,res);
                lastMax = max[i];
            }
        }
        return res;
    }
    //给定一个数值,全部元素的最小值,最大值和分几份,得到该数值应属于哪个区间.
    //注意此公式在左神给的源码中是错误的,(num - min) * 桶的数量len + 1 而不是(num - min) * len
    private  static  int getBucketNum(int num,int min,int max ,int len){
        return (int) (num - min) * (len + 1) / (max - min) ;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_39445556/article/details/104779443