桶排序和基數排序

ZHUO_SIR發表於2018-06-12

一、桶排序

  桶排序是計數排序的升級版。它利用了函式的對映關係,高效與否的關鍵就在於這個對映函式的確定。桶排序 (Bucket sort)的工作的原理:假設輸入資料服從均勻分佈,將資料分到有限數量的桶裡,每個桶再分別排序(有可能再使用別的排序演算法或是以遞迴方式繼續使用桶排序進行排)。

  假設你有五百萬份試卷,每份試卷的滿分都是100分,如果要你對這些試卷按照分數進行排序,天嚕啦,五百萬份試卷啊,快速排序?堆排序?歸併排序?面對這麼多的資料,平均下來上面的每一種演算法至少都要花費nlogn=5000000log5000000=111267433單位時間啊,將近一億多,太慢了。

  要是我們這樣來做呢,首先買101只桶回來,分別為每一隻桶編上0-100標號,我們就只管遍歷一邊所有的試卷,將分數為n的試卷丟入到編號為n的桶裡面,當所有的試卷都放入到相應的桶裡面時,我們就已經將所有的試卷按照分數進行排序了。遍歷一遍所有資料的時間也就是五百萬次,相比與一億多次,那可是省了不少時間。這裡所用到的就是桶排序的思想。

1、演算法描述

1. 設定一個定量的陣列當作空桶;

2. 遍歷輸入資料,並且把資料一個一個放到對應的桶裡去;

3. 對每個不是空的桶進行排序;

4. 從不是空的桶裡把排好序的資料拼接起來。

2、演算法圖解

這裡寫圖片描述

3、演算法demo

#include <bits/stdc++.h>
using namespace std;

using llt = long long int;

void bucket_sort1(vector<int> number)
{
    llt sz = number.size();
    vector<int> bucket;
    for (llt i = 0; i <= 100; ++i)//建立101個桶
        bucket.push_back(0);
    for (llt i = 0; i < sz; ++i)
    {
        bucket[number[i]]++;//把元素放入相應編號的桶中
    }
    for (llt i = 0; i < 100; ++i)
    {
        for (llt j = 0; j < bucket[i]; ++j)//輸出桶的編號(即元素的大小)
            cout << i << " ";
    }
}

void bucket_sort2(vector<int> number)
{
    llt sz = number.size();
    vector<vector<int>> bucket(11);//建立11個桶
    for (llt i = 0; i < sz; ++i)
    {
        //根據十位數放入相應編號的桶中(每個桶中元素的十位數相同)
        bucket[number[i]/10].push_back(number[i]);
    }
    for (llt i = 10; i >= 1; --i)
    {
        if (!bucket[i].empty())
        {
            sort(bucket[i].begin(), bucket[i].end());//對每個桶中的元素排序
            for (auto b : bucket[i])
                cout << b << " ";
        }
    }
}

int main(int argc, char const *argv[])
{
    vector<int> number = {45, 12, 23, 56, 45};
    bucket_sort1(number);
    cout << endl;
    bucket_sort2(number);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

4、演算法總結

  桶排序是穩定排序,對於資料為較小的整數且資料都在某一個範圍內時,使用桶排序效率較高。桶排序最壞情況下是O(n^2),桶排序最好情況下使用線性時間O(n)。 
  桶排序的時間複雜度,取決與對各個桶之間資料進行排序的時間複雜度,因為其它部分的時間複雜度都為O(n)。很顯然,桶劃分的越小,各個桶之間的資料越少,排序所用的時間也會越少。但相應的空間消耗就會增大。

二、基數排序

  基數排序不同於其他的排序演算法,它不是基於比較的演算法。基數排序是一種藉助多關鍵字排序的思想對單邏輯關鍵字進行排序的方法。它是一種穩定的排序演算法。多關鍵字排序中有兩種方法:最高位優先法(MSD)和最低位優先法(LSD)。通常用於對數的排序選擇的是最低位優先法,即先對最次位關鍵字進行排序,再對高一位的關鍵字進行排序,以此類推。

1、演算法描述

1. 取得陣列中的最大數,並取得位數;

2. arr為原始陣列,從最低位開始取每個位組成radix陣列;

3. 對radix進行計數排序(利用計數排序適用於小範圍數的特點);

2、演算法圖解

這裡寫圖片描述

3、演算法demo

#include <bits/stdc++.h>
using namespace std;

//d的值為1、2、3...,表示要求取的相應位的值,1表示求取個位,2表示十分位,類推
int getDigit(int i, int d)
{
    int val;
    while (d--)
    {
        val = i % 10;
        i /= 10;
    }
    return val;
}

//基數排序演算法的具體實現
//begin和end表示資料的起始下標,digit表示資料最大的位數
void RadixSort(vector<int> &list, int begin, int end, int digit)
{
    int radix = 10; //基數
    int i = 0, j = 0;
    vector<int> count(radix, 0);
    vector<int> bucket(end - begin + 1, 0);

    for (int d = 1; d <= digit; d++)
    {
        //置空各個桶的統計資料
        for (i = 0; i < radix; i++)
            count[i] = 0;
        //統計各個桶中所盛資料個數
        for (i = begin; i <= end; i++)
        {
            j = getDigit(list[i], d);
            count[j]++;
        }
        //count[i]表示第i個桶的右邊界索引
        for (i = 1; i < radix; i++)
            count[i] = count[i] + count[i - 1];
        //將資料依次裝入桶中,保證資料的穩定性,此步即為基數排序的分配
        for (i = end; i >= begin; i--)
        {
            j = getDigit(list[i], d);
            bucket[count[j] - 1] = list[i];
            count[j]--;
        }
        //基數排序的收集
        //把桶中的資料再倒出來
        for (i = begin, j = 0; i <= end; i++, j++)
        {
            list[i] = bucket[j];
        }
    }
}

int main(int argc, char const *argv[])
{
    vector<int> number = { 45, 12, 23, 56, 45 };
    RadixSort(number, 0, 4, 2);
    for (auto v : number)
        cout << v << " ";
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

4、演算法總結

  基數排序基於分別排序,分別收集,所以是穩定的。基數排序的時間複雜度是O(n*k),效能比桶排序要略差,每一次關鍵字的桶分配都需要O(n)的時間複雜度,而且分配之後得到新的關鍵字序列又需要O(n)的時間複雜度。假如待排資料可以分為d個關鍵字,則基數排序的時間複雜度將是O(d*2n) ,當然d要遠遠小於n,因此基本上還是線性級別的。

相關文章