比较器(对象排序)
Comparable
具体写的时候总是忘记怎么是升序、降序
总结来看按顺序写就是升序,如:(o1, o2) -> o1.age - o2.age
// 需要实现接口,不能灵活修改排序方式,适合不变的排序
class People implements Comparable<People> {
int age;
String name;
public People(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public int compareTo(People o) {
// 溢出风险:Math.addExact()/Math.subtractExact()
return this.age - o.age;
// return o.age - this.age; 降序
}
}
Comparator
与有序集合一起使用(List、PriorityQueue、TreeSet)
compare
ArrayList<People> list = new ArrayList<>();
list.sort(new Comparator<People>() {
@Override
public int compare(People o1, People o2) {
return o2.age - o1.age; // 降序
}
});
// lambda 写法
// list.sort((o1, o2) -> o2.age - o1.age);
// 字符串实现了compareTo方法
list.sort(new Comparator<People>() {
@Override
public int compare(People o1, People o2) {
return o1.getName().compareTo(o2.getName()); // 按name升序
}
}
comparing / thenComparing / reversed / reverseOrder
// 按age降序排列
list.sort(Comparator.comparing(People::getAge).reversed());
// thenComparing可以往后面加,适用多种排序规则
// 当第一个规则无法分出顺序时用下一个规则判断
排序算法
O(n^2)
冒泡排序
水中的泡泡总是最大的先浮出水面
重复比较相邻元素,若顺序错误就交换它们,每一轮会将最大(或最小)的元素“浮”到数组末尾
public void bubbleSort(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
for (int j = 0; j < nums.length - i - 1; j++) {
if (nums[j] > nums[j + 1]) {
int tem = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tem;
}
}
}
}
选择排序
每次从未排序部分选择最小(或最大)的元素,放到已排序部分的末尾
public void selectedSort(int[] nums) {
// 控制循环次数
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
// 保证nums[i]最小,升序
if (nums[j] < nums[i]) {
int tem = nums[i];
nums[i] = nums[j];
nums[j] = tem;
}
}
}
}
插入排序
打扑克牌时每拿到一张牌就插入合适的位置
将数组分为已排序和未排序两部分,每次从未排序部分取出一个元素,插入到已排序部分的合适位置
public void insertSort(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
// [0, i]已经有序,找到 i + 1 应该存放的位置
for (int j = i + 1; j > 0; j--) {
if (nums[j] > nums[j - 1]) {
// 已经有序,结束遍历
break;
}
int tem = nums[j];
nums[j] = nums[j - 1];
nums[j - 1] = tem;
}
}
}
O(n log n)
归并排序
将一个数组分成两个子数组,分别对这两个子数组进行排序,然后将排好序的子数组合并成一个有序数组
public void mergeSort(int[] nums) {
mergeSort(nums, 0, nums.length - 1);
}
public void mergeSort(int[] nums, int low, int high) {
if (low < high) {
int mid = low + (high - low) / 2;
mergeSort(nums, low, mid);
mergeSort(nums, mid + 1, high);
merge(nums, low, mid + 1, high);
}
}
public void merge(int[] nums, int low, int mid, int high) {
// 两个有序数组的起始下标
int l = low;
int r = mid;
int[] tem = new int[high - low + 1];
int k = 0;
while (l < mid && r <= high) {
if (nums[l] <= nums[r]) {
tem[k++] = nums[l++];
} else {
tem[k++] = nums[r++];
}
}
tem[k++] = l < mid ? nums[l++] : nums[r++];
k = 0;
while (low <= high) {
nums[low++] = tem[k++];
}
}
快速排序
选择一个基准元素,将数组分为两部分,使得左边部分的元素都小于等于基准元素,右边部分的元素都大于基准元素,然后递归地对左右两部分进行排序
public void quickSort(int[] nums) {
quickSort(nums, 0, nums.length - 1);
}
public void quickSort(int[] nums, int low, int high) {
if (low < high) {
int index = partition(nums, low, high);
quickSort(nums, low, index - 1);
quickSort(nums, index + 1, high);
}
}
public int partition(int[] nums, int low, int high) {
int random = low + new Random().nextInt(high - low + 1);
swap(nums, random, high);
int pivot = nums[high];
int i = low;
// 遍历[low, gigh - 1]
for (int j = low; j < high; j++) {
if (nums[j] <= pivot) {
swap(nums, i, j);
i++;
}
}
swap(nums, i, high);
return i;
}
public void swap(int[] nums, int x, int y) {
int tem = nums[x];
nums[x] = nums[y];
nums[y] = tem;
}
堆排序
将数组构建成最大堆(或最小堆),然后依次将堆顶元素(最大值或最小值)与堆的最后一个元素交换,再调整剩余元素使其重新满足堆的性质,重复该过程直到整个数组有序
public void heapSort(int[] nums) {
// 这里不讲堆调整,直接用现成的
PriorityQueue<Integer> queue = new PriorityQueue<>(Comparator.reverseOrder());
for (int num : nums) {
queue.add(num);
}
int i = arr.length - 1;
while (!queue.isEmpty()) {
nums[i--] = queue.poll();
}
}
希尔排序
将原始数据分成多个子序列来改善插入排序的性能,先让元素间隔比较大的距离进行排序,随着间隔逐渐减小,最后间隔为 1 时,就相当于进行普通的插入排序
public void shellSort(int[] nums) {
for (int gap = nums.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < nums.length; i++) {
int j;
for (j = i; nums[j - gap] > nums[j]; j -= gap) {
nums[j] = nums[j - gap];
}
nums[j] = nums[i];
}
}
}
O(n)
计数排序
将输入的数据值转化为键存储在额外开辟的数组空间中。该算法要求输入的数据必须是有确定范围的整数
public void countingSort(int[] nums) {
// 找出最大值和最小值,确认计数数组大小
int max = nums[0];
int min = nums[0];
for (int num: nums) {
if (num > max) {
max = num;
}
if (num < min) {
min = num;
}
}
// 创建计数数组
int[] count = new int[max - min + 1];
// 统计出现次数
for (int num: nums) {
count[num - min]++;
}
// 计算累计计数数组,确定每个元素在结果数组中的位置
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
// 创建结果数组
int[] out = new int[nums.length];
// 从前往后遍历原数组,找到对应的下标放入
// 从后往前可以保证稳定性
for (int i = 0; i < nums.length; i++) {
out[count[nums[i] - min] - 1] = nums[i];
count[nums[i] - min]--;
}
System.arraycopy(out, 0, nums, 0, nums.length);
}
桶排序
将数组分到有限数量的桶子里,每个桶子再分别排序,最后依次把各个桶中的元素合并起来
public void bucketSort(int[] nums) {
// 找最大最小值
int max = nums[0];
int min = nums[0];
for (int num: nums) {
if (num > max) {
max = num;
}
if (num < min) {
min = num;
}
}
// 确定桶个数、大小
int bucketCount = nums.length;
if bucketSize = Math.max(1, (max - min) / bucketCount);
// 创建对应数量的桶
List<List<Integer>> buckets = new ArrayList<>();
for (int i = 0; i < bucketCount; i++) {
buckets.add(new ArrayList<>());
}
// 将元素放入对应桶内
for (int num: nums) {
int index = Math.min(bucketCount - 1, (i - min) / bucketSize);
buckets.get(index).add(num);
}
// 桶内排序,合并
int index = 0;
for (List<Integer> bucket: buckets) {
Collections.sort(bucket); // 可任意选择算法,最坏情况下复杂度降为该算法复杂度
for (int num: bucket) {
nums[index++] = num;
}
}
}
基数排序
将整数按位数切割成不同的数字,然后按每个位数分别比较。它可以从低位到高位(LSD)或者从高位到低位(MSD)进行排序
public void radixSort(int[] nums) {
// 找最大值,确定要排序的最大位数
int max = nums[0];
for (int num: nums) {
if (num > max) {
max = num;
}
}
// 从低位开始,逐位开始排序
for (int i = 1; max / i > 0; i *= 10) {
// i=1个位 i=10十位 i=100百位...
sort(nums, i);
}
}
// 其实就是计数排序
public void sort(int[] nums, int exp) {
int[] out = new int[nums.length];
int[] count = new int[10];
// 统计该位数上数字出现的次数
for (int num: nums) {
count[(num / exp) % 10]++;
}
// 累计计数
for (int i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
for (int i = nums.length - 1; i >= 0; i--) {
int digit = (nums[i] / exp) % 10;
out[count[digit - 1]] = nums[i];
count[digit - 1]--;
}
// 完成当前位的排序
System.arraycopy(out, 0, nums, 0, nums.length);
}