深入解析桶排序演算法及Node.js上JavaScript的代碼實現,node.jsjavascript

來源:互聯網
上載者:User

深入解析桶排序演算法及Node.js上JavaScript的代碼實現,node.jsjavascript

1. 桶排序介紹
桶排序(Bucket sort)是一種基於計數的排序演算法,工作的原理是將資料分到有限數量的桶子裡,然後每個桶再分別排序(有可能再使用別的排序演算法或是以遞迴方式繼續使用桶排序進行排序)。當要被排序的資料內的數值是均勻分配的時候,桶排序時間複雜度為Θ(n)。桶排序不同於快速排序,並不是比較排序,不受到時間複雜度 O(nlogn) 下限的影響。
桶排序按下面4步進行:
(1)設定固定數量的空桶。
(2)把資料放到對應的桶中。
(3)對每個不為空白的桶中資料進行排序。
(4)拼接從不為空白的桶中資料,得到結果。
桶排序,主要適用於小範圍整數資料,且獨立均勻分布,可以計算的資料量很大,而且符合線性期望時間。

2. 桶排序演算法示範
舉例來說,現在有一組資料[7, 36, 65, 56, 33, 60, 110, 42, 42, 94, 59, 22, 83, 84, 63, 77, 67, 101],怎麼對其按從小到大順序排序呢?

操作步驟說明:
(1)設定桶的數量為5個空桶,找到最大值110,最小值7,每個桶的範圍20.8=(110-7+1)/5 。
(2)遍曆未經處理資料,以鏈表結構,放到對應的桶中。數字7,桶索引值為0,計算公式為floor((7 – 7) / 20.8), 數字36,桶索引值為1,計算公式floor((36 – 7) / 20.8)。
(3)當向同一個索引的桶,第二次插入資料時,判斷桶中已存在的數字與新插入數位大小,按照左到右,從小到大的順序插入。如:索引為2的桶,在插入63時,桶中已存在4個數字56,59,60,65,則數字63,插入到65的左邊。
(4)合并非空的桶,按從左至右的順序合并0,1,2,3,4桶。
(5)得到桶排序的結構

3. Nodejs程式實現
像桶排序這種成熟的演算法,自己實現一下並不難,按照上文的思路,我寫了一個簡單的程式實現。我感覺其中最麻煩的部分,是用Javascript操作鏈結表。
現實代碼如下:

'use strict';/////////////////////////////////////////////////// 桶排序/////////////////////////////////////////////////var _this = this  , L = require('linklist');//鏈表/** * 普通數組桶排序,同步 * * @param arr Array 整數數組 * @param num 桶的個數 * * @example: * sort([1,4,1,5,3,2,3,3,2,5,2,8,9,2,1],5) * sort([1,4,1,5,3,2,3,3,2,5,2,8,9,2,1],5,0,5) */exports.sort = function (arr, count) {  if (arr.length == 0) return [];  count = count || (count > 1 ? count : 10);  // 判斷最大值、最小值  var min = arr[0], max = arr[0];  for (var i = 1; i < arr.length; i++) {    min = min < arr[i] ? min : arr[i];    max = max > arr[i] ? max : arr[i];  }  var delta = (max - min + 1) / count;  // console.log(min+","+max+","+delta);  //初始化桶  var buckets = [];  //儲存資料到桶  for (var i = 0; i < arr.length; i++) {    var idx = Math.floor((arr[i] - min) / delta); // 桶索引    if (buckets[idx]) {//非空桶      var bucket = buckets[idx];      var insert = false;//插入標石      L.reTraversal(bucket, function (item, done) {        if (arr[i] <= item.v) {//小於,左邊插入          L.append(item, _val(arr[i]));          insert = true;          done();//退出遍曆        }      });      if (!insert) { //大於,右邊插入        L.append(bucket, _val(arr[i]));      }    } else {//空桶      var bucket = L.init();      L.append(bucket, _val(arr[i]));      buckets[idx] = bucket; //鏈表實現    }  }  var result = [];  for (var i = 0, j = 0; i < count; i++) {    L.reTraversal(buckets[i], function (item) {      // console.log(i+":"+item.v);      result[j++] = item.v;    });  }  return result;}//鏈表格儲存體對象function _val(v) {  return {v: v}}

運行程式:

var algo = require('./index.js');var data = [ 7, 36, 65, 56, 33, 60, 110, 42, 42, 94, 59, 22, 83, 84, 63, 77, 67,101 ];console.log(data);console.log(algo.bucketsort.sort(data,5));//5個桶console.log(algo.bucketsort.sort(data,10));//10個桶

輸出:

[ 7, 36, 65, 56, 33, 60, 110, 42, 42, 94, 59, 22, 83, 84, 63, 77, 67, 101 ][ 7, 22, 33, 36, 42, 42, 56, 59, 60, 63, 65, 67, 77, 83, 84, 94, 101, 110 ][ 7, 22, 33, 36, 42, 42, 56, 59, 60, 63, 65, 67, 77, 83, 84, 94, 101, 110 ]

需要說明的是:

(1)桶內排序,可以像程式中所描述的,在插入過程中實現;也可以插入不排序,在合并過程中,再進行排序,可以調用快度排序。
(2)鏈表,在Node的底層API中,有一個鏈表的實現,我沒有直接使用,而是通過linklist包調用的:https://github.com/nodejs/node-v0.x-archive/blob/master/lib/_linklist.js

4. 案例:桶排序統計高考分數
桶排序最出名的一個應用情境,就是統計高考的分數。一年的全國高考考生人數為900萬人,分數使用標準分,最低200 ,最高900 ,沒有小數,如果把這900萬數字進行排序,應該如何做呢?
演算法分析:
(1)如果使用基於比較的排序,快速排序,平均時間複雜度為O(nlogn) = O(9000000*log9000000)=144114616=1.44億次比較。
(2)如果使用基於計數的排序,桶排序,平均的時候覆雜度,可以控制線上性複雜度,當建立700桶時從200分到900分各一個桶,O(N)=O(9000000),就相當於掃描一次900W條資料。
我們跑一個程式,對比一次快速排序和桶排序。

//產生100W條,[200,900]閉區間的資料var data = algo.data.randomData(1000*1000,200,900);var s1 = new Date().getTime();algo.quicksort.sort(data);//快速排序var s2 = new Date().getTime();algo.bucketsort.sort(data,700);//裝到700個桶var s3 = new Date().getTime();console.log("quicksort time: %sms",s2-s1);console.log("bucket time: %sms",s3-s2);

輸出:

quicksort time: 14768msbucket time: 1089ms

所以,對於高考計分的案例來說,桶排序是更適合的!我們把合適的演算法,用在適合的情境,會給程式帶來超越硬體的效能提升。

5. 桶排序代價分析
BUT....
桶排序利用函數的映射關係,減少了幾乎所有的比較工作。實際上,桶排序的f(k)值的計算,其作用就相當於快排中劃分,已經把大量資料分割成了基本有序的資料區塊(桶)。然後只需要對桶中的少量資料做先進的比較排序即可。
對N個關鍵字進行桶排序的時間複雜度分為兩個部分:
(1) 迴圈計算每個關鍵字的桶映射函數,這個時間複雜度是O(N)。
(2) 利用先進的比較排序演算法對每個桶內的所有資料進行排序,其時間複雜度為  ∑ O(Ni*logNi) 。其中Ni 為第i個桶的資料量。
 很顯然,第(2)部分是桶排序效能好壞的決定因素。盡量減少桶內資料的數量是提高效率的唯一辦法(因為基於比較排序的最好平均時間複雜度只能達到O(N*logN)了)。因此,我們需要盡量做到下面兩點:
(1) 映射函數f(k)能夠將N個資料平均的分配到M個桶中,這樣每個桶就有[N/M]個資料量。
(2) 盡量的增大桶的數量。極限情況下每個桶只能得到一個資料,這樣就完全避開了桶內資料的“比較”排序操作。 當然,做到這一點很不容易,資料量巨大的情況下,f(k)函數會使得桶集合的數量巨大,空間浪費嚴重。這就是一個時間代價和空間代價的權衡問題了。
對於N個待排資料,M個桶,平均每個桶[N/M]個資料的桶排序平均時間複雜度為:

O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)

當N=M時,即極限情況下每個桶只有一個資料時。桶排序的最好效率能夠達到O(N)。

6. 總結
桶排序的平均時間複雜度為線性O(N+C),其中C=N*(logN-logM)。如果相對於同樣的N,桶數量M越大,其效率越高,最好的時間複雜度達到O(N)。 當然桶排序的空間複雜度 為O(N+M),如果輸入資料非常龐大,而桶的數量也非常多,則空間代價無疑是昂貴的。此外,桶排序是穩定的。
 其實我個人還有一個感受:在尋找演算法中,基於比較的尋找演算法最好的時間複雜度也是O(logN)。比如折半尋找、平衡二叉樹、紅/黑樹狀結構等。但是Hash表卻有O(C)線性層級的尋找效率(不衝突情況下尋找效率達到O(1))。大家好好體會一下:Hash表的思想和桶排序是不是有一曲同工之妙呢?

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.