計數排序,桶排序與基數排序

通常算法能作到O(logn),已經很是不錯,若是咱們排序的對象是純數字,還能夠作到驚人的O(n)。涉及的算法有計數排序、基數排序、桶排序,它們被歸類爲非比較排序。html

非比較排序只要肯定每一個元素以前的已有的元素個數便可,遍歷一次就能求解。算法時間複雜度O(n)。git

非比較排序時間複雜度低,但因爲非比較排序須要佔用空間來肯定惟一位置。因此對數據規模和數據分佈有必定的要求。算法

計數排序

計數排序須要佔用大量空間,它僅適用於數據比較集中的狀況。好比 [0~100],[10000~19999] 這樣的數據。數組

咱們看一下計數排序是怎麼運做,假設咱們有[1,2,3,1,0,4]這六個數,這裏面最大的值爲4,那麼咱們建立一個長度爲4的數組,每一個元素默認爲0。這至關於選舉排序,一共有6個投票箱,1就投1號箱,0就投入0號箱。注意,這些箱原本就是已經排好序,而且箱的編號就是表明原數組的元素。當所有投完時,0號箱有1個,1號箱有2個,2號箱有1個,3號箱有1,4號箱有1個。而後咱們從這些箱的全部數依次出來,放到新數組,就神奇地排好序了。app

計數排序沒有對元素進行比較,只是利用了箱與元素的一一對應關係,根據箱已經排好序的先決條件,解決排序。oop

//by 司徒正美
function countSort(arr){
   var max = Math.max.apply(0, arr);
   var buckets = []
   for(var i = 0; i < n; i++){
      var el = arr[i]
      if(buckets[el]){//子桶裏不實際存在
         buckets[el]++ 
      }else{
         buckets[el] = 1
      }
   }
   var index = 0
   for(var i = 0; i < n; i++){
       var m = buckets[i].length;
       while(m){
          arr[index] = i;
          index++
          m--
       }
   }
   return arr
}

但數組有一個問題就是它的索引值是從0開始,但咱們的元素也要大於或等於0。咱們能夠經過一個數學技巧讓它支持負數。spa

//by 司徒正美
function countSort(arr){
   var max = arr[0]
   var min = arr[0]
   for(var i = 0; i < n; i++){
      if(arr[i] > max){
         max = arr[i]
      }
      if(arr[i] < min){
         max = arr[i]
      }
   }
 
   var buckets = new Array(max-min+1).fill(0);
   for(var i = 0; i < n; i++){
      buckets[ arr[i]-min ]++     //減去最小值,確保索引大於負數
   }
   var index = 0, bucketCount = max-min+1
   for(var i = 0; i < bucketCount; i++){
       var m = buckets[i].length;
       while(m){
        //將桶的編號加上最小值,變回原來的元素
        arr[index] = i+min;
        index++
        m--
       }
   }
   return arr
}

桶排序

桶排序與計數排序很類似,不過如今的桶不單計數,是實實在在地放入元素。舉個例子,學校要對全部老師按年齡進行排序,這麼多老師很難操做,那麼先讓他們按年齡段進行分組,20-30歲的一組,30-40歲一組,50-60歲一組,而後組內再排序。這樣效率就大大提升了。桶排序也是於這種思想。.net

操做步驟:3d

  1. 確認範圍,亦即求取原數組的最大值與最小值。
  2. 確認須要多少個桶(這個一般做爲參數傳入,不能大於原數組長度),而後最大值減最小值,除以桶的數量,但得每一個桶最多能放多個元素,咱們稱這個數爲桶的最大容量。
  3. 遍歷原數組的全部元素,除以這個最大容量,就能獲得它要放入的桶的編號了。在放入時可使用插入排序,也能夠在合併時才使用快速排序。
  4. 對全部桶進行遍歷,若是桶內的元素已經排好序,直接一個個取出來,放到結果數組就好了。
//by 司徒正美
var arr = [2,5,3,0,2,8,0,3,4,3]
   function bucketSort(array, num){
    if(array.length <= 1){
      return array
    }
    var n = array.length;
    var min = Math.min.apply(0, array)
    var max = Math.max.apply(0, array)
    if(max === min){
       return array
    }
    var capacity = (max - min + 1) /num;
    var buckets = new Array(max - min + 1)
    for(var i = 0; i < n; i++){
      var el = array[i];//el多是負數
      var index = Math.floor((el - min) / capacity)
      var bucket = buckets[index]
      if(bucket){
         var jn = bucket.length;
         if(el >= bucket[jn-1]){
            bucket[jn] = el
         }else{
            insertSort: 
            for(var j = 0; j < jn; j++){
                if(bucket[j] > el){
                    while(jn > j){ //所有向後挪一位
                        bucket[jn] = bucket[jn-1]
                        jn--
                    }
                    bucket[j] = el //讓el佔據bucket[j]的位置
                    break insertSort;
                }
            }
         }
      }else{
         buckets[index] = [el]
      }
    }
    var index = 0
    for(var i = 0; i < num; i++){
        var bucket = buckets[i]
        for(var k = 0, kn = bucket.length; k < kn; k++){
            array[index++] = bucket[k]
        }
    }
    return array;
 }
 console.log(  bucketSort(arr,4) )
 //[ 0, 0, 2, 2, 3, 3, 3, 4, 5, 8 ]

基數排序

基數排序是一種非比較型的整數排序算法。其基本原理是,按照整數的每一個位數分組。在分組過程當中,對於不足位的數據用0補位。code

基數排序按照對位數分組的順序的不一樣,能夠分爲LSD(Least significant digit)基數排序和MSD(Most significant digit)基數排序。

LSD基數排序,是按照從低位到高位的順序進行分組排序。MSD基數排序,是按照從高位到低位的順序進行分組排序。上述兩種方式不只僅是對位數分組順序不一樣,其實現原理也是不一樣的。

LSD基數排序

對於序列中的每一個整數的每一位均可以當作是一個桶,而該位上的數字就能夠認爲是這個桶的鍵值。好比下面數組

[170, 45, 75, 90, 802, 2, 24, 66]

首先咱們要確認最大值,一個for循環得最大數,由於最大數的位數最長。

而後,創建10個桶,亦即10個數組。

而後再遍歷全部元素,取其個位數,個位數是什麼就放進對應編號的數組,1放進1號桶。

0號桶: 170,90
 1號桶: 無
 2號桶: 802,2
 3號桶: 無
 4號桶: 24
 5號桶: 45, 75
 6號桶: 66
 7-9號桶: 無

而後再依次將元素從桶裏最出來,覆蓋原數組,或放到一個新數組,咱們把這個通過第一次排序的數組叫sorted。

sorted = [170,90,802,2,24,45,75,66]

而後咱們再一次遍歷sorted數組的元素,此次取十位的值。這時要注意,2不存在十位,那麼默認爲0

0號桶: 2,802
 1號桶: 無
 2號桶: 24
 3號桶: 無
 4號桶: 45
 5號桶: 無
 6號桶: 66
 7號桶: 170, 75
 8號桶: 無
 9號桶: 90

再所有取出來

sorted = [2,802,24,45,66,170,75,90]

開始百位上的入桶操做,沒有百位就默認爲0:

0號桶: 2,24,45,66,75,90
 1號桶: 170
 2-7號桶:無
 8號桶: 802
 9號桶: 無

再所有取出來

sorted = [2,24,45,66,75,90,170,802]

沒有千位數,那麼循環結束,返回結果桶sorted

從程序描述以下:

image_1c44h9tg4j23p1l18e1ojue8i20.png-122.3kB

//by 司徒正美
function radixSort(array) {
    var max = Math.max.apply(0, array);
    var times = getLoopTimes(max),
        len = array.length;
    var buckets = [];
    for (let i = 0; i < 10; i++) {
        buckets[i] = []; //初始化10個桶
    }
    for (var radix = 1; radix <= times; radix++) {
        //個位,十位,百位,千位這樣循環
        lsdRadixSort(array, buckets, len, radix);
    }
    return array;
}
// 根據數字某個位數上的值獲得桶的編號
function getBucketNumer(num, d) {
    return (num + "").reverse()[d];
}
//或者這個
function getBucketNumer(num, i) {
    return Math.floor((num / Math.pow(10, i)) % 10);
}
//獲取數字的位數
function getLoopTimes(num) {
    var digits = 0;
    do {
        if (num > 1) {
            digits++;
        } else {
            break;
        }
    } while ((num = num / 10));
    return digits;
}
function lsdRadixSort(array, buckets, len, radix) {
    //入桶
    for (let i = 0; i < len; i++) {
        let el = array[i];
        let index = getBucketNumer(el, radix);
        buckets[index].push(el);
    }
    var k = 0;
    //重寫原桶
    for (let i = 0; i < 10; i++) {
        let bucket = buckets[i];
        for (let j = 0; j < bucket.length; j++) {
            array[k++] = bucket[j];
        }
        bucket.length = 0;
    }
}
// test
var arr = [278, 109, 63, 930, 589, 184, 505, 269, 8, 83];
console.log(radixSort(arr));

MSD基數排序

接下來說MSD基數排序.

最開始時也是遍歷全部元素,取最大值,獲得最大位數,創建10個桶。這時從百位取起。不足三位,對應位置爲0.

0號桶: 45, 75, 90, 2, 24, 66
 1號桶: 107
 2-7號桶: 無
 8號桶: 802
 9號桶: 無

接下來就與LSD不同。咱們對每一個長度大於1的桶進行內部排序。內部排序也是用基數排序。咱們須要創建另10個桶,對0號桶的元素進行入桶操做,這時比原來少一位,亦即十位。

0號桶: 2
 1號桶: 無
 2號桶: 24
 3號桶: 無
 4號桶: 45
 5號桶: 無
 6號桶: 66
 7號桶: 75
 8號桶: 無
 9號桶: 90

而後繼續遞歸上一步,所以每一個桶的長度,都沒有超過1,因而開始0號桶的收集工做:

0號桶: 2,24,45,66,75,90
 1號桶: 107
 2-7號桶: 無
 8號桶: 802
 9號桶: 無

將這步驟應用其餘桶,最後就排序完畢。

//by 司徒正美
function radixSort(array) {
    var max = Math.max.apply(0, array),
        times = getLoopTimes(max),
        len = array.length;
    msdRadixSort(array, len, times);
    return array;
}

//或者這個
function getBucketNumer(num, i) {
    return Math.floor((num / Math.pow(10, i)) % 10);
}
//獲取數字的位數
function getLoopTimes(num) {
    var digits = 0;
    do {
        if (num > 1) {
            digits++;
        } else {
            break;
        }
    } while ((num = num / 10));
    return digits;
}
function msdRadixSort(array, len, radix) {
    var buckets = [[], [], [], [], [], [], [], [], [], []];
    //入桶
    for (let i = 0; i < len; i++) {
        let el = array[i];
        let index = getBucketNumer(el, radix);
        buckets[index].push(el);
    }
    //遞歸子桶
    for (let i = 0; i < 10; i++) {
        let el = buckets[i];
        if (el.length > 1 && radix - 1) {
            msdRadixSort(el, el.length, radix - 1);
        }
    }
    var k = 0;
    //重寫原桶
    for (let i = 0; i < 10; i++) {
        let bucket = buckets[i];
        for (let j = 0; j < bucket.length; j++) {
            array[k++] = bucket[j];
        }
        bucket.length = 0;
    }
}
var arr = radixSort([170, 45, 75, 90, 802, 2, 24, 66]);
console.log(arr);

字符串使用基數排序實現字典排序

此外,基數排序不侷限於數字,能夠稍做變換,就能應用於字符串的字典排序中。咱們先來一個簡單的例子,只對都是小寫字母的字符串數組進行排序。

小寫字母一共26個,考慮到長度不同的狀況,咱們須要對夠短的字符串進行補充,這時補上什麼好呢?咱們不能直接上0,而是補空白。而後根據字母與數字的對應關係,弄27個桶,空字符串對應0,a對應1,b對應2.... 字典排序是從左邊開始比較, 所以咱們須要用到MST基數排序。

//by 司徒正美
var character = {};
"abcdefghijklmnopqrstuvwxyz".split("").forEach(function(el, i) {
    character[el] = i + 1;
});
function toNum(c, length) {
    var arr = [];
    arr.c = c;
    for (var i = 0; i < length; i++) {
        arr[i] = character[c[i]] || 0;
    }
    return arr;
}
function getBucketNumer(arr, i) {
    return arr[i];
}

function radixSort(array) {
    var len = array.length;
    var loopTimes = 0;

    //求出最長的字符串,並得它的長度,那也是最高位
    for (let i = 0; i < len; i++) {
        let el = array[i];
        var charLen = el.length;
        if (charLen > loopTimes) {
            loopTimes = charLen;
        }
    }

    //將字符串轉換爲數字數組
    var nums = [];
    for (let i = 0; i < len; i++) {
        nums.push(toNum(array[i], loopTimes));
    }
    //開始多關鍵字排序
    msdRadixSort(nums, len, 0, loopTimes);
    //變回字符串
    for (let i = 0; i < len; i++) {
        array[i] = nums[i].c;
    }
    return array;
}

function msdRadixSort(array, len, radix, radixs) {
    var buckets = [];
    for (var i = 0; i <= 26; i++) {
        buckets[i] = [];
    }
    //入桶
    for (let i = 0; i < len; i++) {
        let el = array[i];
        let index = getBucketNumer(el, radix);
        buckets[index].push(el);
    }
    //遞歸子桶
    for (let i = 0; i <= 26; i++) {
        let el = buckets[i];
        //el.c是用來識別是桶仍是咱們臨時建立的數字字符串
        if (el.length > 1 && !el.c && radix < radixs) {
            msdRadixSort(el, el.length, radix + 1, radixs);
        }
    }
    var k = 0;
    //重寫原桶
    for (let i = 0; i <= 26; i++) {
        let bucket = buckets[i];
        for (let j = 0; j < bucket.length; j++) {
            array[k++] = bucket[j];
        }
        bucket.length = 0;
    }
}
var array = ["ac", "ee", "ef", "b", "z", "f", "ep", "gaaa", "azh", "az", "r"];

var a = radixSort(array);
console.log(a);

參考連接

相關文章
相關標籤/搜索