桶排序 -- 計數排序

桶排序是不基於比較的排序,是一個大的概念.其 實現 有計數排序和基數排序.

桶排序受數據狀況本身影響特別嚴重.

 

計數排序

如果知道一個待排序數組中的值是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) ;
    }
}

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章