通常算法能作到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
//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基數排序,是按照從高位到低位的順序進行分組排序。上述兩種方式不只僅是對位數分組順序不一樣,其實現原理也是不一樣的。
對於序列中的每一個整數的每一位均可以當作是一個桶,而該位上的數字就能夠認爲是這個桶的鍵值。好比下面數組
[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
從程序描述以下:
//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基數排序.
最開始時也是遍歷全部元素,取最大值,獲得最大位數,創建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);