有道算法题--排序之桶排序实现求排序后相邻最大差值问题

1,285 阅读5分钟

前言

一直误以为写文章太耗费费时间,昨日为尊敬高贵帅气逼人的导师所一语惊醒(未一鸣惊人之日,绝不提尊师名讳,嗯,没错),分享才是程序员最快的提升; 今天开始,做不到多写多练,就不是诚实善良的南方小菜

不废话,直接进入主题

预备知识

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

算法稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,A1=A2,且A1在A2之前,而在排序后的序列中A1仍在A2之前,则称这种排序算法是稳定的;否则称为不稳定的。

--------------------------- 我是分割线--------------------------------——————————————————————————————————

以上是教材定义,非小菜所讲,其实一个概念均可用几个特性来描述,对于桶排序,简单来说有如下特性:

  1. 非基于比较
  2. 运用到了桶的概念
  3. 时间复杂度O(N) 空间复杂度O(N)
  4. 不普遍,存在瓶颈,具有稳定性

    ???????????????????????????????????

什么鬼?啥叫桶?咋就时间空间复杂度O(N)了?看客莫急,这样划分咱就可以一一破之啦(在下面的题目中会更加体现);

特性解释

  1. 非基于比较
    一提到排序,大家脑海里必然不自觉浮现出ifelse以及C语言老师神秘莫测的微笑,是的,无论是古老的冒泡、玄学的归并、扑克牌(发哥?)式插入等等等,都不可避免的进行了元素与元素之间的比较,并非不好,只是百家争鸣,思想的万花筒让桶排序这一方式出现,他的实现思想并不是比较,而是分治,即进行范围区间划分,然后将数据按规律放入,这样放置好了之后,由于桶本身就存在顺序,也就自然而然地实现了处理数据的一定规律排序,却没有用到彼此之间的比较

  2. 何为桶?其实就是一个容器,他可以是链表、双向链表、集合等等等,这个容器的特征在于,他保存了一个状态(即上述的一段区间)下出现的词频(即你需要的这个区间内的数据)
  3. 复杂度计算
    当你要创建桶,必然需要耗费额外的空间,长度为N===》空间复杂度O(N) 而如下上述,按状态依次放置数据于桶中,需要遍历数据====》时间复杂度O(N)
    • 稳定性:简单来说,一个萝卜一个坑,数据对着已排好的桶进行放置,必然稳定;
    • 瓶颈:与被排序的实际数据状态有关,所以不具有普遍性

题解

重点来了,题解才是最好的理解

请听题

  • 给定一个数组,求如果排序之后,相邻两个数的最大差值,要求时间复杂度O(N),且要求不能用非基于比较的排序
解题思路
  1. 非基于比较 ===== 这已经是很明显的提示我们要用桶排序了
  2. 要求时间复杂度O(N) ====== 桶!!!!
  3. 假设有N个数据的数组arr,其中最大值max,最小值min,
    1. 数据对应桶的下标的计算:parseInt((num-min)*N/max-min)
    2. 定义两个数组容器,分别存储对应桶的min和max
    3. 遍历非空桶min与下一个非空桶的max的差值,最大差值即为解
代码实现(个人习惯多写注释,所以大家可以参考一下注释内容)
// 给定一个数组,求如果排序之后,相邻两个数的最大差值,要求时间复杂度O(N),且要求不能用非基于比较的排序

class MaxGap {
    constructor(){

    }
    
    getMax(arr){
        if(arr == null || arr.length<2) return 0;
        // 数组长度 数组中最大最小值
        let len = arr.length,min= arr[0],max= arr[0];
        // 桶中是否有数 每个桶中的最大值 最小值
        let hasNum=[],maxs=[],mins=[];
        // 遍历数组取最大最小值
        for(let i = 0; i<len;i++){
          
            min = Math.min(min,arr[i]);
            max = Math.max(max,arr[i]);
            // console.log(min,max)
        }
        if(min == max) return 0;
        let bid = 0;
        // 循环遍历 将数放在对应范围的桶中 只保留桶中数的max和min 并且将桶的状态改为true
        for(let i = 0;i<len;i++){
            bid = this.bucket(arr[i],len,min,max);
            mins[bid] = hasNum[bid] ? Math.min(mins[bid],arr[i]) : arr[i];
            maxs[bid] = hasNum[bid] ? Math.max(maxs[bid],arr[i]) : arr[i];
            hasNum[bid] = true;
            // console.log(hasNum)
        }
        // 最大差值
        let res = 0;

        let lastMax = maxs[0];
        let i = 1;
        // console.log(hasNum)
        for(;i<=len;i++){
            // 遍历非空桶min与下一个非空桶的max的差值,最大差值即为res   
            // 注意,空桶只是杀死了同一个桶中的值的差值不可能为最大,而并不能保证最大差值一定在空桶附近出现
            if(hasNum[i]){
                res = Math.max(res, mins[i] - lastMax);
                lastMax = maxs[i]
            }
        }
        console.log(mins,maxs)
        return res;

    }

    /**
     * 求出num对应桶的索引
     * @param {目标数} num 
     * @param {数组长度} len 
     * @param {数组最小值} min 
     * @param {数组最大值} max 
     */
    bucket(num,len,min,max){
        // console.log(num,len,min,max)
        // console.log((num - min) * len / (max - min))
        return parseInt((num - min) * len / (max - min));
    }


}
console.log(new MaxGap().getMax([1,3,100,9,8]));