每天一道演算法題--排序之桶排序實現求排序後相鄰最大差值問題

南方小菜發表於2019-04-09

前言

一直誤以為寫文章太耗費費時間,昨日為尊敬高貴帥氣逼人的導師所一語驚醒(未一鳴驚人之日,絕不提尊師名諱,嗯,沒錯),分享才是程式設計師最快的提升; 今天開始,做不到日更,就不是誠實善良的南方小菜啦

每天一道演算法題--排序之桶排序實現求排序後相鄰最大差值問題

不廢話,直接進入主題

預備知識

桶排序(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]));

複製程式碼

相關文章