【每日算法】桶排序算法

1)算法簡介

桶排序 (Bucket sort)或所謂的箱排序,是一個排序算法,工做的原理是將數組分到有限數量的桶子裏。每一個桶子再個別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排序)。ios

桶排序是穩定的,且在大多數狀況下常見排序裏最快的一種,比快排還要快,缺點是很是耗空間,基本上是最耗空間的一種排序算法,並且只能在某些情形下使用。面試

2)算法描述和分析

桶排序具體算法描述以下:
一、設置一個定量的數組看成空桶子。
二、尋訪串行,而且把項目一個一個放到對應的桶子去。
三、對每一個不是空的桶子進行排序。
四、從不是空的桶子裏把項目再放回原來的串行中。算法

桶排序最好狀況下使用線性時間O(n),很顯然桶排序的時間複雜度,取決與對各個桶之間數據進行排序的時間複雜度,由於 其它部分的時間複雜度都爲O(n);很顯然,桶劃分的越小,各個桶之間的數據越少,排 序所用的時間也會越少。但相應的空間消耗就會增大。數組

能夠證實,即便選用插入排序做爲桶內排序的方法,桶排序的平均時間複雜度爲線性。 具體證實,請參考算法導論。其空間複雜度也爲線性。spa

3)算法圖解、flash演示、視頻演示

圖解
桶排序設計

Flash:
能夠參考http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=90中的過程code

視頻:
這裏就不給出桶排序的視頻了,見上flash吧視頻

4)算法代碼

#include <time.h>  
#include <iostream>  
#include <iomanip>  
using namespace  std;  
  
/*initial arr*/  
void InitialArr(double *arr,int n) {  
    srand((unsigned)time(NULL));  
    for (int i = 0; i<n;i++) {  
        arr[i] = rand()/double(RAND_MAX+1);   //(0.1)  
    }  
}  
  
/* print arr*/  
void PrintArr(double *arr,int n) {  
    for (int i = 0;i < n; i++) {  
        cout<<setw(15)<<arr[i];  
        if ((i+1)%5 == 0 || i ==  n-1) {  
            cout<<endl;  
        }  
    }  
}  
  
void BucketSort(double * arr,int n) {  
    double **bucket = new double*[10];  
    for (int i = 0;i<10;i++) {  
        bucket[i] = new double[n];  
    }  
    int count[10] = {0};  
    for (int i = 0 ; i < n ; i++) {  
        double temp = arr[i];  
        int flag = (int)(arr[i]*10); //flag標識小樹的第一位   
        bucket[flag][count[flag]] = temp; //用二維數組的每一個向量來存放小樹第一位相同的數據  
        int j = count[flag]++;  
  
        /* 利用插入排序對每一行進行排序 */  
        for(;j > 0 && temp < bucket[flag][j - 1]; --j) {  
            bucket[flag][j] = bucket[flag][j-1];  
        }  
        bucket[flag][j] =temp;  
    }  
  
    /* 全部數據從新連接 */  
    int k=0;  
    for (int i = 0 ; i < 10 ; i++) {  
        for (int j = 0 ; j< count[i];j++) {  
            arr[k] = bucket[i][j];  
            k++;  
        }  
    }  
    for (int i = 0 ; i<10 ;i++) {  
        delete bucket[i];  
        bucket[i] =NULL;  
    }  
    delete []bucket;  
    bucket = NULL;  
}  
  
void main() {  
    double *arr=new double[10];  
    InitialArr(arr, 10);  
    BucketSort(arr, 10);  
    PrintArr(arr,10);  
    delete [] arr;  
}

5)考察點、重點和頻度分析

桶排序是一種很巧妙的排序方法,在處理密集型數排序的時候有比較好的效果(主要是這種狀況下空間複雜度不高),其思想也可用在不少算法題上,詳見後續筆試面試算法例題。排序

6)筆試面試題

例題一、一年的全國高考考生人數爲500 萬,分數使用標準分,最低100 ,最高900 ,沒有小數,你把這500 萬元素的數組排個序。遞歸

對500W數據排序,若是基於比較的先進排序,平均比較次數爲O(5000000*log5000000)≈1.112億。可是咱們發現,這些數據都有特殊的條件: 100=<score<=900。那麼咱們就能夠考慮桶排序這樣一個「投機取巧」的辦法、讓其在毫秒級別就完成500萬排序。

建立801(900-100)個桶。將每一個考生的分數丟進f(score)=score-100的桶中。這個過程從頭至尾遍歷一遍數據只須要500W次。而後根據桶號大小依次將桶中數值輸出,便可以獲得一個有序的序列。並且能夠很容易的獲得100分有人,501分有人。

實際上,桶排序對數據的條件有特殊要求,若是上面的分數不是從100-900,而是從0-2億,那麼分配2億個桶顯然是不可能的。因此桶排序有其侷限性,適合元素值集合並不大的狀況。

例題二、在一個文件中有 10G 個整數,亂序排列,要求找出中位數。內存限制爲 2G。只寫出思路便可(內存限制爲 2G的意思就是,可使用2G的空間來運行程序,而不考慮這臺機器上的其餘軟件的佔用內存)。

分析: 既然要找中位數,很簡單就是排序的想法。那麼基於字節的桶排序是一個可行的方法。
思想:將整型的每1byte做爲一個關鍵字,也就是說一個整形能夠拆成4個keys,並且最高位的keys越大,整數越大。若是高位keys相同,則比較次高位的keys。整個比較過程相似於字符串的字典序。按如下步驟實施:

一、把10G整數每2G讀入一次內存,而後一次遍歷這536,870,912即(102410241024)*2 /4個數據。每一個數據用位運算">>"取出最高8位(31-24)。這8bits(0-255)最多表示255個桶,那麼能夠根據8bit的值來肯定丟入第幾個桶。最後把每一個桶寫入一個磁盤文件中,同時在內存中統計每一個桶內數據的數量,天然這個數量只須要255個整形空間便可。
二、繼續之內存中的整數的次高8bit進行桶排序(23-16)。過程和第一步相同,也是255個桶。
三、一直下去,直到最低字節(7-0bit)的桶排序結束。我相信這個時候徹底能夠在內存中使用一次快排就能夠了。

例題三、給定n個實數x1,x2,...,xn,求這n個實數在實軸上相鄰2個數之間的最大差值M,要求設計線性的時間算法
典型的最大間隙問題。
要求線性時間算法。須要使用桶排序。桶排序的平均時間復發度是O(N).若是桶排序的數據分佈不均勻,假設都分配到同一個桶中,最壞狀況下的時間複雜度將變爲O(N^2).

桶排序: 最關鍵的建桶,若是桶設計得很差的話桶排序是幾乎沒有做用的。一般狀況下,上下界有兩種取法,第一種是取一個10^n或者是2^n的數,方便實現。另外一種是取數列的最大值和最小值而後均分做桶。

對於這個題,最關鍵的一步是:由抽屜原理知:最大差值M>= (Max(V[n])-Min(V[n]))/(n-1)!因此,假如以(Max(V[n])-Min(V[n]))/(n-1)爲桶寬的話,答案必定不是屬於同一個桶的兩元素之差。所以,這樣建桶,每次只保留桶裏面的最大值和最小值便可。

代碼以下:

//距離平均值爲offset = (arrayMax - arrayMin) / (n - 1), 則距離最大的數必然大於這個值  
//每一個桶只要記住桶中的最大值和最小值,依次比較上一個桶的最大值與下一個桶的最小值的差值,找最大的便可.  
#include <iostream>  
#define MAXSIZE 100    //實數的個數  
#define MAXNUM 32767  
using namespace std;  
struct Barrel  
{  
 double min;   //桶中最小的數  
 double max;   //桶中最大的數  
 bool flag;   //標記桶中有數  
};  
int BarrelOperation(double* array, int n)  
{  
 Barrel barrel[MAXSIZE];  //實際使用的桶  
 int nBarrel = 0;  //實際使用桶的個數  
 Barrel tmp[MAXSIZE];   //臨時桶,用於暫存數據  
 double arrayMax = -MAXNUM, arrayMin = MAXNUM;  
 for(int i = 0; i < n; i++) {  
  if(array[i] > arrayMax)  
   arrayMax = array[i];  
  if(array[i] < arrayMin)  
   arrayMin = array[i];  
 }  
 double offset = (arrayMax - arrayMin) / (n - 1);  //全部數的平均間隔  
 //對桶進行初始化  
 for(i = 0; i < n; i++) {    
  tmp[i].flag = false;  
  tmp[i].max = arrayMin;  
  tmp[i].min = arrayMax;  
 }  
 //對數據進行分桶  
 for(i = 0; i < n; i++) {     
  int pos = (int)((array[i] - arrayMin) / offset);  
  if(!tmp[pos].flag) {  
   tmp[pos].max = tmp[pos].min = array[i];  
   tmp[pos].flag = true;  
  } else {  
   if(array[i] > tmp[pos].max)  
    tmp[pos].max = array[i];  
   if(array[i] < tmp[pos].min)  
    tmp[pos].min = array[i];  
  }   
 }  
 for(i = 0; i <= n; i++) {  
  if(tmp[i].flag)   
   barrel[nBarrel++] = tmp[i];     
 }  
 int maxOffset = 0.0;  
 for(i = 0; i < nBarrel - 1; i++) {  
  if((barrel[i+1].min - barrel[i].max) > maxOffset)   
   maxOffset = barrel[i+1].min - barrel[i].max;  
 }  
 return maxOffset;  
}  
int main()  
{  
 double array[MAXSIZE] = {1, 8, 6, 11, 7, 13, 16, 5};  //所需處理的數據  
 int n = 8; //數的個數  
 //double array[MAXSIZE] = {8, 6, 11};  
 //int n = 3;  
 int maxOffset = BarrelOperation(array, n);  
 cout << maxOffset << endl;  
 return 0;  
}
相關文章
相關標籤/搜索