排序

公共方法

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
   /**
* 数组长度校验
*
* @param arr 待校验的数组
* @return 当数组为空或者数组长度小于2时, 不用排序, 直接返回
*/
private static boolean checkArray(int[] arr) {
// 当数组为空或者数组长度小于2时,不用排序
return arr == null || arr.length < 2;
}

/**
* 交换数据 -- 数组
*
* @param arr 数组
* @param i 待交换的值
* @param j 待交换的值
*/
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
// 使用异或运算实现交换操作
// arr[i] = arr[i] ^ arr[j];
// arr[j] = arr[i] ^ arr[j];
// arr[i] = arr[i] ^ arr[j];
}

各个排序

排序方法 时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性
冒泡排序 O(n2)O(n^{2}) O(n2)O(n^{2}) O(n)O(n) O(1)O(1) 稳定
插入排序 O(n2)O(n^{2}) O(n2)O(n^{2}) O(n)O(n) O(1)O(1) 稳定
归并排序 O(nlog2n)O(n\log_{2}{n}) O(nlog2n)O(n\log_{2}{n}) O(nlog2n)O(n\log_{2}{n}) O(n)O(n) 稳定
选择排序 O(n2)O(n^{2}) O(n2)O(n^{2}) O(n2)O(n^{2}) O(1)O(1) 不稳定
快速排序 O(nlog2n)O(n\log_{2}{n}) O(n2)O(n^{2}) O(nlog2n)O(n\log_{2}{n}) O(nlog2n)O(n\log_{2}{n}) 不稳定
堆排序 O(nlog2n)O(n\log_{2}{n}) O(nlog2n)O(n\log_{2}{n}) O(nlog2n)O(n\log_{2}{n}) O(1)O(1) 不稳定
希尔排序 O(n1.3)O(n^{1.3}) O(n2)O(n^{2}) O(n)O(n) O(1)O(1) 不稳定
计数排序 O(n+k)O(n+k) O(n+k)O(n+k) O(n+k)O(n+k) O(n+k)O(n+k) 稳定
桶排序 O(n+k)O(n+k) O(n2)O(n^{2}) O(n)O(n) O(n+k)O(n+k) 稳定
基数排序 O(nk)O(n*k) O(nk)O(n*k) O(nk)O(n*k) O(n+k)O(n+k) 稳定

冒泡排序

  • 思想

    • 比较两个相邻的元素,将值大的元素交换到右边.
    • 如果遇到相等的值不进行交换,那这种排序方式是稳定的排序方式.
    • N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次
  • 实现

    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
        /**
    * 冒泡排序
    *
    * @param arr 待排数组
    */
    public static void bubbleSort(int[] arr) {
    if (checkArray(arr)) {
    return;
    }
    for (int i = arr.length - 1; i > 0; i--) {
    for (int j = 0; j < i; j++) {
    // 比较相邻的元素
    if (arr[j] > arr[j + 1]) {
    swap(arr, j, j + 1);
    }
    }
    }
    }

    // 稳定性
    // 示例(升序排序): 6(1),5,4,3,2,6(2)
    // 第一轮: 5,4,3,2,6(1),6(2)
    // 第二轮: 4,3,2,5,6(1),6(2)
    // 第三轮: 3,2,4,5,6(1),6(2)
    // 第四轮: 2,3,4,5,6(1),6(2)

插入排序

  • 思想

    为了给要插入的元素腾出空间,需要将其余所有元素在插入之前都向右移动一位.

    插入排序所需的时间取决于输入中元素的初始顺序.

  • 实现

    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
        /**
    * 插入排序
    *
    * @param arr 待排数组
    */
    public static void insertionSort(int[] arr) {
    if (checkArray(arr)) {
    return;
    }
    // 插入排序
    // i作为指针作用遍历整个数组
    for (int i = 1; i < arr.length; i++) {
    // 先比较arr[j] 与 arr[j+1]
    // 当满足arr[j] > arr[j + 1] 说明顺序不对,则进行交换 j向前移动一位 继续判断是否满足arr[j] > arr[j + 1]
    // 满足就进行交换 直至j<0或者不满足arr[j] > arr[j + 1]
    // 当不满足arr[j] > arr[j + 1] 说明顺序是对的,则直接判断下一个位置
    for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
    swap(arr, j, j + 1);
    }
    // 或者写成这样
    // for (int j = i; j > 0 && arr[j-1] > arr[j]; j--) {
    // swap(arr, j, j - 1);
    // }

    }
    }

    /**
    * 改进后的插入排序
    * -- 减少交换次数
    *
    * @param arr 待排数组
    */
    public static void insertionSort2(int[] arr) {
    if (checkArray(arr)) {
    return;
    }
    for (int i = 1; i < arr.length; i++) {
    // 寻找元素arr[i]合适的插入位置
    int e = arr[i];
    // j保存元素e应该插入的位置
    int j;
    for (j = i; j > 0 && arr[j - 1] > e; j--) {
    // 前一位数向后移动一位
    arr[j] = arr[j - 1];
    }
    arr[j] = e;
    }
    }

归并排序

  • 思想

    归并排序(merge sort)是利用 归并 的思想实现的排序方法,该算法采用经典的 分治(divide-and-conquer)策略

    • 分(divide):将问题分成一些小的问题,然后递归求解
    • 治(conquer):将分的阶段得到的各答案「修补」在一起

    即:分而治之

  • 实现

    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
    63
    64
    65
    66
    67
    68
    69
    70
    /**
    * 归并排序
    *
    * @param arr 待排数组
    */
    public static void mergeSort(int[] arr) {
    if (arr == null || arr.length < 2) {
    return;
    }
    mergeSort(arr, 0, arr.length - 1);
    }

    /**
    * 对区间[l,r]的数组进行归并排序
    * -- 利用递归划分大数组为小数组进行排序,再将排完序的小数组进行合并
    *
    * @param arr 待排数组
    * @param l 左边界
    * @param r 右边界
    */
    private static void mergeSort(int[] arr, int l, int r) {
    if (l == r) {
    return;
    }
    // 获取中间索引
    int mid = l + (r - l) / 2;
    // 对左边进行归并排序
    mergeSort(arr, l, mid);
    // 对右边进行归并排序
    mergeSort(arr, mid + 1, r);
    // 对排完序的数组进行合并
    merge(arr, l, mid, r);
    }

    /**
    * 将数组arr中的区间[l,m]和[m+1,r]的数合并,使arr有序
    *
    * @param arr 待排数组
    * @param l 左边界索引
    * @param m 中间索引
    * @param r 右边界索引
    */
    private static void merge(int[] arr, int l, int m, int r) {
    // 临时辅助数组
    int help[] = new int[r - l + 1];
    // 游标用于控制help
    int i = 0;
    // arr数组的[l,r]的左边索引,用于辅助拷贝
    int left = l;
    // arr数组的[l,r]的右边部分左边界,用于辅助拷贝
    int mid = m + 1;
    // 将arr数组的区间[l,mid]和区间[mid+1,r]的值拷贝进help数组
    while (left <= m && mid <= r) {
    // 将区间[l,r]与区间[mid+1,r]中叫小的数将入help数组
    // 然后将游标向右移动
    help[i++] = arr[left] < arr[mid] ? arr[left++] : arr[mid++];
    }
    // 其中left和mid必然会有一个会越界,从而退出循环
    // 所以要将未加入help数组的数从left/mid开始将后续未加入help数组的数加入help数组
    while (left <= m) {
    help[i++] = arr[left++];
    }
    while (mid <= r) {
    help[i++] = arr[mid++];
    }
    // 将排完序的数组help中的值赋给原数组arr
    for (int j = 0; j < help.length; j++) {
    arr[l + j] = help[j];
    }
    }

选择排序

  • 思想

    • 第一趟从n个元素的数据序列中选出关键字最小/大的元素并放在最前/后位置,下一趟从n-1个元素中选出最小/大的元素并放在最前/后位置。以此类推,经过n-1趟完成排序。
  • 实现

    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
        /**
    * 选择排序
    * -- 擂台法
    *
    * @param arr 待排序的数组
    */
    public static void selectionSort(int[] arr) {
    if (checkArray(arr)) {
    return;
    }
    int k;
    int j;
    for (int i = 0; i < arr.length - 1; i++) {
    // 设置擂主 k
    k = i;
    for (j = i + 1; j < arr.length; j++) {
    if (arr[k] > arr[j]) {
    k = j;
    }
    }
    // 当擂主改变进行交换 减少交换次数
    if (k != i) {
    swap(arr, k, i);
    }
    }
    }
    // 示例(升序排序): 6(1),5,4,3,6(2),2
    // 第一轮: 2,5,4,3,6(2),6(1)
    // 第二轮: 2,3,4,5,6(2),6(1)
    // 第三轮: 2,3,4,5,6(2),6(1)
    // 第四轮: 2,5,4,3,6(2),6(1)
    // 第五轮: 2,5,4,3,6(2),6(1)

快速排序

  • Quicksort 快速排序 是对 冒泡排序的一种改进。

  • 思想

    通过一趟排序将要排序的数据 分割成独立的两个部分,一部分的所有数据都比另外一部分的所有数据都要小。

    然后再按如上的方法对这两部分数据分别进行快速排序,排序过程可以递归进行,以此达到整个数据变成有序序列。

    1. 挑选的基准值为数组中间的值
    2. 中间值就把数组分成了两组
    3. 左边一组,从左到右,挨个与 基准值 比较,找出比基准值大的值
    4. 右边一组,从右到左,挨个与 基准值 比较,找出比基准值小的值
    5. 左右两边各找到一个值,那么就让这两个值进行交换
    6. 然后继续找,直到左右两边碰到,这一轮就结束。这一轮就称为快速排序
    7. 继续对分出来的小组,进行上述的快速排序操作,直到组内只剩下一个数时,则排序完成
    1
    2
    3
    l ------------ pivot --------------- r
    一组从左往右找 一组从右往左找

  • 实现

    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
    63
    64
    65
    66
    /**
    * 随机快速排序
    *
    * @param arr 待排数组
    */
    public static void quickSort(int[] arr) {
    if (checkArray(arr)) {
    return;
    }
    quickSort(arr, 0, arr.length - 1);
    }

    /**
    * @param arr 待排数组
    * @param l 待排序左边界(小于区域)
    * @param r 待排序右边界(大于区域)
    * @Description: 随机快速排序
    */
    private static void quickSort(int[] arr, int l, int r) {
    if (l < r) {
    // 进行随机交换
    swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
    //
    int[] p = partition(arr, l, r);
    // 对左边区域进行快排
    quickSort(arr, l, p[0] - 1);
    // 对右边区域进行快排
    quickSort(arr, p[1] + 1, r);
    }
    }

    /**
    * @param arr 待分区的数组
    * @param l 左边界
    * @param r 右边界
    * @return 包含相等区域边界值的数组
    * @description: 将数组分为三个区域(> x, = x, < x)
    */
    private static int[] partition(int[] arr, int l, int r) {
    // 记录小于区域的边界
    int less = l - 1;
    // 记录大于区域的边界
    int more = r;
    // l作为游标向右移动,当l>=more时说明不需要进行分区了
    while (l < more) {
    if (arr[l] < arr[r]) {
    // 将最后一个数即arr[r]作为参照数
    // 当arr[l]<arr[r],时说明arr[l]属于小于区域,将小于区域边界的下一个值arr[++less]的值与arr[l]的值进行交换,
    // 并将小于区域向右移动一格(小于区域进行扩大),因为arr[r]已经进行了区域划分,所有游标也向右移动一格(l++).
    swap(arr, ++less, l++);
    } else if (arr[l] > arr[r]) {
    // 当arr[l]>arr[r]时,说明arr[l]属于大于区域,将arr[--more]的值与arr[l]的值进行交换
    // 并将大于区域向左移动一格(大于区域进行扩大),因为交换的arr[l]并没有进行区域划分,所以游标l不动,等待下次进行区域划分
    swap(arr, --more, l);
    } else {
    // 相等区域
    // 进行移动游标,判断下一个数
    l++;
    }
    }
    // 上述操作中没有将参照数进行划分
    // 所以最后将参数和大于区域的边界进行交换
    swap(arr, more, r);
    // 最后返回包含相等区域边界的数组
    return new int[]{less + 1, more};
    }

堆排序

  • 实现

    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
    63
    64
    65
    66
    67
    68
    69
    70
    /**
    * 堆排序
    * -- 大根堆
    *
    * @param arr 待排数组
    */
    public static void heapSort(int[] arr) {
    if (arr == null || arr.length < 2) {
    return;
    }
    // 把arr数组构建为大根堆的形式
    for (int i = 0; i < arr.length; i++) {
    heapInsert(arr, i);
    }
    int size = arr.length;
    // 将最后一个数和第一个数交换 -- 最后一个位置确定
    // --size将排好序的数踢出待排序列
    swap(arr, 0, --size);
    // 从头结点重新调整大根堆
    while (size > 0) {
    heapify(arr, 0, size);
    swap(arr, 0, --size);
    }
    }

    /**
    * 从index位置开始,左右两个孩子的最大值和父亲节点位置进行交换
    *
    * @param arr 原数组
    * @param index index位置
    * @param size 堆的大小
    */
    private static void heapify(int[] arr, int index, int size) {
    // 左孩子的索引
    int left = index * 2 + 1;
    while (left < size) {
    // 在left+1(右孩子的索引)不越界的情况下,比较左右孩子的值,取出其中较大值的索引
    int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
    // 将左右孩子中的较大值和index所表示的值进行比较
    largest = arr[largest] > arr[index] ? largest : index;

    // 若较大值大于父亲的值,交换largest所表示的值与父亲节点的值
    // 再将原父亲节点的索引改为largest -- 来方便对下一层次的进行调整
    if (largest == index) {
    break;
    }
    swap(arr, largest, index);
    index = largest;
    // 从largest为父亲节点继续往下调整
    left = index * 2 + 1;
    }

    }

    /**
    * 插入数据构建大根堆
    *
    * @param arr 原数组
    * @param index 当前节点的索引
    */
    private static void heapInsert(int[] arr, int index) {
    // arr[index] -- 当前节点位置
    // arr[(index-1)/2] -- 当前节点的父亲节点位置
    // 若当前节点的值大于其父亲节点,将其与其父亲节点进行交换,并把当前位置的索引改为原父亲节点位置
    while (arr[index] > arr[(index - 1) / 2]) {
    swap(arr, index, (index - 1) / 2);
    index = (index - 1) / 2;
    }

    }

希尔排序

  • 对于大规模乱序驻足插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点点地从数组的一端移动到另一端.希尔排序为了加快速度简单地进行插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序.

  • 思想

    希尔排序的思想是使数组中任意间隔为h的元素都有序的.这样的数组被称为h有序数组.换句话说,一个h有序数组就是h个互相独立的有序数组编织在一起组成的一个数组.

  • 优点

  • 缺点

  • 实现

    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
    /**
    * 希尔排序
    * 非稳定
    * 思路:
    * 例: 9 8 7 6 5 4 3 2 1
    * 一趟一增量,用增量进行分组,组内进行插入排序
    *
    * @param arr 待排数组
    */
    static void shellSort(int[] arr) {
    for (int interval = arr.length / 2; interval > 0; interval /= 2) {
    for (int i = interval; i < arr.length; i++) {
    int target = arr[i];
    int j = i - interval;
    while (j >= 0 && target < arr[j]) {
    arr[j + interval] = arr[j];
    j -= interval;
    }
    arr[j + interval] = target;
    }
    }
    }

    /**
    * 来自算法(第四版)希尔排序
    *
    * @param arr
    */
    static void shellSortFromAlgs4(int[] arr) {
    int N = arr.length;
    int h = 1;
    while (h < N / 3) {
    // 1,4,13,40,121,364
    h = 3 * h + 1;
    }
    while (h >= 1) {
    // 将数组变为h有序
    for (int i = h; i < N; i++) {
    for (int j = i; j >= h && arr[j] - arr[j - h] < 0; j -= h) {
    swap(arr, j, j - h);
    }
    }
    h = h / 3;
    }
    }

计数排序

  • 计数排序(Counting Sort)是一种O(n)的排序算法,其思路是开一个长度为maxValue-minValue+1的数组,然后

    1. 分配。扫描一遍原始数组,以当前值-minValue作为下标,将该下标的计数器增1。
    2. 收集。扫描一遍计数器数组,按顺序把值收集起来。
  • 思路

    ① 找出待排序的数组中最大和最小的元素
    ② 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
    ③ 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
    ④ 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

  • 实现

    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
    /**
    * 计数排序
    * 参考于 https://www.cnblogs.com/xiaochuan94/p/11198610.html
    * @param arr 待排数组
    */
    static int[] countiongSort(int[] arr) {
    // 找出数组A中的最大值、最小值
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for (int num : arr) {
    max = Math.max(max, num);
    min = Math.min(min, num);
    }
    // 初始化计数数组count
    // 长度为最大值减最小值加1
    int[] count = new int[max - min + 1];
    // 对计数数组各元素赋值
    for (int num : arr) {
    // A中的元素要减去最小值,再作为新索引
    count[num - min]++;
    }
    // 计数数组变形,新元素的值是前面元素累加之和的值
    for (int i = 1; i < count.length; i++) {
    count[i] += count[i - 1];
    }
    // 创建结果数组
    int[] result = new int[arr.length];
    // 遍历A中的元素,填充到结果数组中去
    for (int i : arr) {
    result[count[i - min] - 1] = i;
    count[i - min]--;
    }
    return result;
    }

桶排序

  • 实现

    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
    /**
    * 桶排序 -- 计数排序
    *
    * @param arr 待排数组
    */
    static void bucketSort(int[] arr) {
    if (arr == null || arr.length < 2) {
    return;
    }
    int max = Integer.MIN_VALUE;
    // 找出数组中最大值
    for (int i = 0; i < arr.length; i++) {
    max = Math.max(max, arr[i]);
    }
    // 定义一个辅助数组长度为max+1
    int[] bucket = new int[max + 1];
    // 统计arr数组中每个数出现的频率
    for (int i = 0; i < arr.length; i++) {
    bucket[arr[i]]++;
    }
    // 将桶中每个数从桶中倒出,根据每个数出现的频率
    int i = 0;
    for (int j = 0; j < bucket.length; j++) {
    while (bucket[j]-- > 0) {
    arr[i++] = j;
    }
    }
    }

基数排序

  • 思想

    1. 将所有待比较数值 统一为同样的数位长度,数位较短的数 前面补零
    2. 然后从最低位开始,依次进行一次排序
    3. 这样从最低位排序一直到最高位排序完成以后,序列就变成了一个有序序列
  • 优点

    • 基数排序是对 传统桶排序 的扩展,速度很快
  • 缺点

    • 是经典的空间换时间的方式,占用内存空间很大
      • 当数据量太大的时候,所耗费的额外空间较大。是原始数据的 10 倍空间
  • 实现

    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
    /**
    * 基数排序
    * 参考于 https://zq99299.github.io/dsalg-tutorial/dsalg-java-hsp/07/09.html#%E5%AE%8C%E6%95%B4%E5%AE%9E%E7%8E%B0
    * @param arr 待排数组
    */
    static void radixSort(int[] arr) {
    // 1. 得到数组中的最大值,并获取到该值的位数。用于循环几轮
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
    if (arr[i] > max) {
    max = arr[i];
    }
    }
    // 得到位数
    int maxLength = String.valueOf(max).length();

    // 定义桶(0~9) 和 标识桶中元素个数
    int[][] bucket = new int[10][arr.length];
    int[] bucketCounts = new int[bucket.length];

    // 总共需要进行 maxLength 轮
    for (int k = 1, n = 1; k <= maxLength; k++, n *= 10) {
    // 进行桶排序
    for (int i = 0; i < arr.length; i++) {
    // 获取该轮的桶索引:每一轮按 10 的倍数递增,获取到对应数位数
    // 这里额外使用一个步长为 10 的变量 n 来得到每一次递增后的值
    int bucketIndex = arr[i] / n % 10;
    // 放入该桶中
    bucket[bucketIndex][bucketCounts[bucketIndex]] = arr[i];
    // 标识该桶元素多了一个
    bucketCounts[bucketIndex]++;
    }
    // 将桶中元素获取出来,放到原数组中
    int index = 0;
    for (int i = 0; i < bucket.length; i++) {
    if (bucketCounts[i] == 0) {
    // 该桶无有效元素,跳过不获取
    continue;
    }
    // 获取桶中有效的个数
    for (int j = 0; j < bucketCounts[i]; j++) {
    arr[index++] = bucket[i][j];
    }
    // 取完后,重置该桶的元素个数为 0 ,下一次才不会错乱数据
    bucketCounts[i] = 0;
    }
    }
    }