前言

之前学习了基于比较的各种排序算法java实现各种排序算法(比较排序),今天再学习一下非比较排序。

计数排序

代码实现

以对所有学生的成绩排序为例

import java.util.Arrays;
import java.util.Random;

public class CountingSort {

  public void sort(int[] nums) {
    //成绩的范围为[0,100]
    int[] scores = new int[101];
    for (int num : nums) {
      scores[num]++;
    }
    int index = 0;
    for (int score = 0; score < scores.length; score++) {
      //scores[score]的值为score分数的学生人数
      for (int i = 0; i < scores[score]; i++) {
        nums[index++] = score;
      }
    }
  }

  public static void main(String[] args) {
    //生成一个包含50个元素的数组(每个元素小于等于100)
    int[] arr = generateRandomArr(50, 101);
    System.out.println(Arrays.toString(arr));
    new CountingSort().sort(arr);
    System.out.println(Arrays.toString(arr));
  }

  private static int[] generateRandomArr(int n, int bound) {
    Random random = new Random();
    int[] arr = new int[n];
    for (int i = 0; i < n; i++) {
      arr[i] = random.nextInt(bound);
    }
    return arr;
  }

}

统统计出每个成绩的学生个数,如60分学生有10个,61分学生有20个。计数排序适用于数据范围比较小的情况,如学生成绩,年龄等。时间复杂度O(N)。

应用

leetcode75. 颜色分类

基数排序

import java.util.Arrays;

public class LSDSort {

  public void sort(String[] arr) {
    if (arr.length == 0) {
      return;
    }
    int length = arr[0].length();
    for (String s : arr) {
      if (s.length() != length) {
        throw new IllegalArgumentException("All Strings' length must be the same.");
      }
    }
    int R = 256;
    int[] counts = new int[R];
    String[] temp = new String[arr.length];
    int[] countIndexs = new int[R];
    for (int r = length - 1; r >= 0; r--) {
      //对每一位进行计数排序
      Arrays.fill(counts, 0);
      for (String s : arr) {
        counts[s.charAt(r)]++;
      }
      for (int i = 0; i < R; i++) {
        if (i == 0) {
          countIndexs[i] = 0;
        } else {
          countIndexs[i] = countIndexs[i - 1] + counts[i - 1];
        }
      }
      for (String s : arr) {
        temp[countIndexs[s.charAt(r)]] = s;
        countIndexs[s.charAt(r)]++;
      }
      System.arraycopy(temp, 0, arr, 0, arr.length);
    }
  }

  public static void main(String[] args) {

    String[] arr = {"BCA", "CAB", "ACB", "BAC", "ABC", "CBA"};
    new LSDSort().sort(arr);
    for (String s : arr) {
      System.out.println(s);
    }
  }
}

从低位开始,对每一位进行计数排序,适用于等长字符串,如车牌号,手机号等。

桶排序

还是以对所有学生的成绩排序为例,分成10个桶,第一个桶成绩区间为[0-9],第二个为[10-19],依次类推

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class BucketSort {

  public void sort(int[] nums) {
    //成绩的范围为[0,100] 桶大小为10  11个桶
    int bucketSize = 10;
    int scoreRange = 101;
    int bucketCount = scoreRange / bucketSize + (scoreRange % bucketSize == 0 ? 0 : 1);
    List<Integer>[] bucketList = new List[bucketCount];
    for (int i = 0; i < bucketList.length; i++) {
      bucketList[i] = new LinkedList<>();
    }
    for (int num : nums) {
      bucketList[getBucketIndex(num, bucketSize)].add(num);
    }
    List<Integer> res = new LinkedList<>();
    for (List<Integer> list : bucketList) {
      int[] array = list.stream().mapToInt(x -> x).toArray();
      insertSort(array);
      for (int val : array) {
        res.add(val);
      }
    }
    int index = 0;
    for (Integer val : res) {
      nums[index++] = val;
    }
  }

  private int getBucketIndex(int num, int bucketSize) {
    return num / bucketSize;
  }

  private void insertSort(int[] nums) {
    //[0,i)为已排序的,[i,n)为未排序的
    for (int i = 1; i < nums.length; i++) {
      int insertValue = nums[i];
      int j = i - 1;
      while (j >= 0 && insertValue < nums[j]) {
        nums[j + 1] = nums[j];
        j--;
      }
      nums[j + 1] = insertValue;
    }
  }

  public static void main(String[] args) {
    int[] nums = {12, 56, 87, 99, 64, 5, 23};
    new BucketSort().sort(nums);
    System.out.println(Arrays.toString(nums));
  }

}

学生成绩区间为[0,100],每个桶区间为10,对每个桶内元素进行插入排序,然后所有桶中元素顺序拿出来即可。桶排序适用于数据分布均匀的情况。

总结

  • 计数排序可以看做桶排序的特例,当每个桶大小为1时,就变成了计数排序。
  • 基数排序可以看做多轮计数排序,对每个有效位都进行一次计数排序。
  • 这三种排序算法都需要额外的辅助空间,且对输入数据有要求,只能在特定情况下使用。

参考

计数排序、基数排序和桶排序