Day-15 线性时间演算法 : Bucket sort

bucket sort(桶排序)

假设输入平均分布,也就是输入的阵列每一种组合情况都是机率均等的,平均情况下他的时间复杂度为https://chart.googleapis.com/chart?cht=tx&chl=O(n)。和counting sort类似,都是对於输入做出一些假设,才达到如此快的执行速度,counting sort假设阵列中的每一个元素都是界於1到n之间的整数,而bucket sort也同样具有这项性质。

假设我们有https://chart.googleapis.com/chart?cht=tx&chl=n个元素的阵列需要排序,每个元素https://chart.googleapis.com/chart?cht=tx&chl=A%5Bi%5D都界於某一个数的区间,如https://chart.googleapis.com/chart?cht=tx&chl=1https://chart.googleapis.com/chart?cht=tx&chl=100

  1. 建立桶子 : 建立https://chart.googleapis.com/chart?cht=tx&chl=k个桶子,每一个桶子对应到某一个预期范围的数字区间,如第一个桶子放置https://chart.googleapis.com/chart?cht=tx&chl=1https://chart.googleapis.com/chart?cht=tx&chl=10的元素,第二个桶子放置https://chart.googleapis.com/chart?cht=tx&chl=11https://chart.googleapis.com/chart?cht=tx&chl=20的元素,也就是假设元素在https://chart.googleapis.com/chart?cht=tx&chl=1https://chart.googleapis.com/chart?cht=tx&chl=n之间,我们就在等分的划分成https://chart.googleapis.com/chart?cht=tx&chl=k个区域,将每个区域视为桶子,由於我们假设输入平均分布,因此不大会出现所有元素都在同一个桶子的情况。
  2. 放置元素 : 将阵列中的元素放到对应的桶子。
  3. 排序 : 使用排序演算法对於每一个桶子中的元素进行排序。
  4. 走访 : 依序走访每一个桶子中的元素,并且将元素放回原来的阵列,完成排序。

在bucket sort的程序码中,我们假设输入为包含https://chart.googleapis.com/chart?cht=tx&chl=n个元素的阵列https://chart.googleapis.com/chart?cht=tx&chl=A,每一个元素https://chart.googleapis.com/chart?cht=tx&chl=A%5Bi%5D介於某一个数的区间,假设https://chart.googleapis.com/chart?cht=tx&chl=1https://chart.googleapis.com/chart?cht=tx&chl=100,此外,我们还需要额外的空间,https://chart.googleapis.com/chart?cht=tx&chl=B%5B0..n-1%5D当作桶子来存放元素,https://chart.googleapis.com/chart?cht=tx&chl=B阵列中存放的为链结串列(linked list),方便我们进行走访。

BUCKET-SORT(A)
n = A.length
let B[0...n-1] be a new array
for i = 0 to n - 1
    make B[i] an empty list
for i = 1 to n
    insert A[i] into list B[A[i]/10]
for i = 0 to n - 1
    sort list B[i] with insertion sort
concatenate the lists B[0],B[1],...,B[n - 1] together in order


以上面这个例子,就是将阵列中每一个元素依照其数字范围,如0到10, 11到20依次放入桶子中,接着使用insertion sort对箱子内的元素进行排序,箱子皆为一个链结串列,将桶内元素串接再一起,接着从0号箱子开始走访不是空的桶子,得到排序完成的阵列。

下面是C++实作

#include <iostream>
#include <vector>

using namespace std;
void bucket_sort(int *, int);
void insertion_sort(vector<int> &);
int main(void)
{
    int A[10] = {78, 17, 39, 26, 72, 94, 21, 12, 23, 68};
    bucket_sort(A, 10);
    for (auto i : A)
    {
        cout << i << ' ';
    }
}

void bucket_sort(int *A, int A_length)
{
    vector<int> B[10];
    for (int i = 0; i < A_length; i++)
    {
        int B_index = A[i] / 10;
        B[B_index].push_back(A[i]);
    }
    for (int i = 0; i < 10; i++)
    {
        insertion_sort(B[i]);
    }
    int A_index = 0;
    for (int i = 0; i < 10; i++)
    {
        for (int j = 0; j < B[i].size(); j++)
        {
            A[A_index++] = B[i][j];
        }
    }
}

void insertion_sort(vector<int> &B)
{
    for (int i = 1; i < B.size(); i++)
    {
        int key = B[i];
        int j = i - 1;
        while (j >= 0 && B[j] > key)
        {
            B[j + 1] = B[j];
            j--;
        }
        B[j + 1] = key;
    }
}

bucket sort效率

在虚拟码中总共有3个for回圈,回圈主体中除了排序以外,最坏情况下时间复杂度皆为https://chart.googleapis.com/chart?cht=tx&chl=O(n)。假设我们使用的排序为插入排序,需要呼叫n次插入排序。

假设https://chart.googleapis.com/chart?cht=tx&chl=n_i表示桶子https://chart.googleapis.com/chart?cht=tx&chl=B%5Bi%5D中元素个数的随机变数,插入排序在最差的情况下时间复杂度为https://chart.googleapis.com/chart?cht=tx&chl=O(n%5E2),因此整个bucket sort在最差情况下的时间复杂度可以用下面的式子表示:
https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20T(n)%20%3D%5CTheta(n)%20%2B%5Csum_%7Bi%20%3D%200%7D%5E%7Bn-1%7DO(n%5E2)

下面分析bucket sort在平均情况下的执行时间,也就是对输入的数据取期望值,我们可以得到执行时间的期望值。
https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20E%5BT(n)%5D%20%3DE%5B%5CTheta(n)%2B%5Csum_%7Bi%3D0%7D%5E%7Bn-1%7DO(n%5E2)%5D%5C%5C%3D%5CTheta(n)%2B%5Csum_%7Bi%3D0%7D%5E%7Bn-1%7DE%5BO(n%5E2)%5D%5C%5C%3D%5CTheta(n)%2B%5Csum_%7Bi%3D0%7D%5E%7Bn-1%7DO(E%5B%7Bn_i%7D%5E2%5D)

而这里要先求出https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20E%5B%7Bn_i%7D%5E2%5D这一项,我们使用指示器随机变数进行处理,对所有https://chart.googleapis.com/chart?cht=tx&chl=i%20%3D%200..n-1https://chart.googleapis.com/chart?cht=tx&chl=j%3D1...nhttps://chart.googleapis.com/chart?cht=tx&chl=i表示桶子的index, https://chart.googleapis.com/chart?cht=tx&chl=j表示阵列中元素的index,https://chart.googleapis.com/chart?cht=tx&chl=I表示由桶子所组成的阵列,https://chart.googleapis.com/chart?cht=tx&chl=A表示待排序的元素所在的阵列
https://chart.googleapis.com/chart?cht=tx&chl=X_%7Bij%7D%3DI%5Cbegin%7BBmatrix%7DA%5Bj%5D%5C%20falls%5C%20in%5C%20bucket%5C%20i%5Cend%7BBmatrix%7D
这里https://chart.googleapis.com/chart?cht=tx&chl=X_%7Bij%7D表示https://chart.googleapis.com/chart?cht=tx&chl=A%5Bj%5D放入桶子的事件,发生表示1,反之为0。https://chart.googleapis.com/chart?cht=tx&chl=n_i表示桶子https://chart.googleapis.com/chart?cht=tx&chl=B%5Bi%5D中元素个数的随机变数,因此这里可以使用指示器随机变数的和去表示元树的随机个数
https://chart.googleapis.com/chart?cht=tx&chl=n_i%3D%5Csum_%7Bj%3D1%7D%5EnX_%7Bij%7D

接着为了计算https://chart.googleapis.com/chart?cht=tx&chl=E%5B%7Bn_i%7D%5E2%5D,将平方项展开
https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20E%5B%7Bn_1%7D%5E2%5D%3DE%5B(%5Csum_%7Bj%3D1%7D%5EnX_%7Bij%7D)%5E2%5D%3DE%5B%5Csum_%7Bj%3D1%7D%5En%5Csum_%7Bk%3D1%7D%5EnX_%7Bij%7DX%7Bik%7D%5D%3DE%5B%5Csum_%7Bj%3D1%7D%5EnX_%7Bij%7D%5E2%2B%5Csum_%7B1%3C%3Dj%3C%3Dn%7D%5Csum_%7B1%3C%3Dk%3C%3Dn%2C%5C%5Ck!%3D%20j%7DX_%7Bij%7DX_%7Bik%7D%5Dhttps://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20%5C%5C%3D%5Csum_%7Bj%3D1%7D%5EnE%5BX_%7Bij%7D%5E2%5D%2BE%5B%5Csum_%7Bj%3D1%7D%5EnX_%7Bij%7D%5E2%2B%5Csum_%7B1%3C%3Dj%3C%3Dn%7D%5Csum_%7B1%3C%3Dk%3C%3Dn%2C%5C%5Ck!%3D%20j%7DE%5BX_%7Bij%7DX_%7Bik%7D%5D

指示器随机变数https://chart.googleapis.com/chart?cht=tx&chl=X_%7Bij%7D,表示https://chart.googleapis.com/chart?cht=tx&chl=A%5Bj%5D放入桶子https://chart.googleapis.com/chart?cht=tx&chl=I%5Bi%5D的事件,发生的机率为https://chart.googleapis.com/chart?cht=tx&chl=1%2Fn,当此事件发生时,https://chart.googleapis.com/chart?cht=tx&chl=X_%7Bij%7D为1,因此
https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20E%5BX_%7Bij%7D%5E2%5D%3D1%5E2*%5Cfrac%201%20n%2B0%5E2*(1-%5Cfrac%201%20n)%3D%5Cfrac%201%20n
而在https://chart.googleapis.com/chart?cht=tx&chl=k!%3Dj时,
https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20E%5BX_%7Bij%7DX_%7Bik%7D%5D%3DE%5BX_%7Bij%7D%5DE%5BX_%7Bik%7D%5D%3D%5Cfrac%201%20n%20*%20%5Cfrac%201%20n%20%3D%20%5Cfrac%201%20%7Bn%5E2%7D

将以上两个式子代回https://chart.googleapis.com/chart?cht=tx&chl=E%5B%7Bn_i%7D%5E2%5D

https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20E%5B%7Bn_i%7D%5E2%5D%3D%5Csum_%7Bj%3D1%7D%5EnE%5BX_%7Bij%7D%5E2%5D%2BE%5B%5Csum_%7Bj%3D1%7D%5EnX_%7Bij%7D%5E2%2B%5Csum_%7B1%3C%3Dj%3C%3Dn%7D%5Csum_%7B1%3C%3Dk%3C%3Dn%2C%5C%5Ck!%3D%20j%7DE%5BX_%7Bij%7DX_%7Bik%7D%5D%20https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20%5C%5C%3D%5Csum_%7Bj%3D1%7D%5En%5Cfrac%201%20n%2BE%5B%5Csum_%7Bj%3D1%7D%5EnX_%7Bij%7D%5E2%2B%5Csum_%7B1%3C%3Dj%3C%3Dn%7D%5Csum_%7B1%3C%3Dk%3C%3Dn%2C%5C%5Ck!%3D%20j%7D%5Cfrac%201%20%7Bn%5E2%7D%5C%5C%3Dn*%5Cfrac%201%20n%2Bn(n-1)*%5Cfrac%201%20%7Bn%5E2%7D%5C%5C%3D1%2B%5Cfrac%20%7Bn-1%7D%20n%5C%5C%3D2%20-%20%5Cfrac%201%20n

而我们上面得到的执行时间期望值为
https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20T(n)%20%3D%5CTheta(n)%20%2B%5Csum_%7Bi%20%3D%200%7D%5E%7Bn-1%7DO(n%5E2)
https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20E%5BT(n)%5D%20%3DE%5B%5CTheta(n)%2B%5Csum_%7Bi%3D0%7D%5E%7Bn-1%7DO(n%5E2)%5D%5C%5C%3D%5CTheta(n)%2B%5Csum_%7Bi%3D0%7D%5E%7Bn-1%7DE%5BO(n%5E2)%5D%5C%5C%3D%5CTheta(n)%2B%5Csum_%7Bi%3D0%7D%5E%7Bn-1%7DO(E%5B%7Bn_i%7D%5E2%5D)

https://chart.googleapis.com/chart?cht=tx&chl=E%5B%7Bn_i%7D%5E2%5D代换,得到
https://chart.googleapis.com/chart?cht=tx&chl=%5Cdisplaystyle%20%5CTheta(n)%2Bn*O(2-1%2Fn)%3D%5CTheta(n)

所以即使输入的输入不是均匀分布的,也就是数据不一定式平均分在散某一个区间,bucket sort还是能够在线性时间内完成,只要符合以下性质

  • 所有桶大小的平方和与阵列中元素的总数呈现线性关系

那麽bucket sort还是可以在线性时间内完成。

p.s: 感谢MuMu大大介绍displaystyle这个排版方式~~

参考资料:Introduciton to algorithms 3rd


<<:  DAY11 Kotlin基础 条件语句

>>:  GCP loadbalance(一)

哈罗,世界!

Photo by KOBU Agency on Unsplash 文章同步发布於:https://...

【Day 1_ 来聊聊Arm选出的2021七大手游流行趋势】

想了想....还是决定换题目主攻gaming这块! 第一篇文章就决定从分享Arm生态系资深经理Ste...

[13th][Day7] container 处理程序

在 container 中运行後台任务 docker exec -d daemon_eric tou...

DAY28 第一个完整程序练习,一台计算机!(三)

今天我们要来讲剩下的方法 public void time(View view){ if (reco...

[Android Studio菜鸟的学习分享]我不是机器人-Google reCAPTCHA

Google reCAPTCHA是Google开发的防堵机器人验证API, 原本是设计给网页使用, ...