Arrays.sort()的底层实现
练习:912. 排序数组
对于序列中的相同元素,如果排序之后它们的相对位置没有发生改变,则称该排序算法为「稳定排序」,反之则为「不稳定排序」
应用:
如果单单排序 int 数组,那么稳定性没有什么意义。但如果排序一些结构比较复杂的数据,那么稳定性排序就有更大的优势了。
比如说你有若干订单数据,已经按照订单号排好序了,现在你想对订单的交易日期再进行排序:
如果用稳定排序算法(比如归并排序),那么这些订单不仅按照交易日期排好了序,而且相同交易日期的订单的订单号依然是有序的。
但如果你用不稳定排序算法(比如快速排序),那么虽然排序结果会按照交易日期排好序,但相同交易日期的订单的订单号会丧失有序性。
在实际工程中我们经常会将一个复杂对象的某一个字段作为排序的 key,所以应该关注编程语言提供的 API 底层使用的到底是什么排序算法,是稳定的还是不稳定的,这很可能影响到代码执行的效率甚至正确性。
2022/2 - 7/27
算法思想:以最左边的数字为基准数,两个哨兵分别位于数组的左右两端,右端的先出发,遇到比基准数小或相等的就停下,此时左边哨兵出发,遇到比基准数大的的就停下,然后交换两个数字的,重复这个过程,直到两个哨兵相遇,此时将相遇时的数字与基准数交换,这样基准数左边都是比基准数小或相等的,右边都是比基准数大的,分别在左右两部分做递归操作即可;
void sort(int[] nums, int lo, int hi) {
if (lo >= hi) {
return;
}
int p = partition(nums, lo, hi);
sort(nums, lo, p - 1);
sort(nums, p + 1, hi);
}
int partition(int[] nums, int lo, int hi) {
int pivot = nums[lo];
int i = lo + 1, j = hi;
while (i <= j) { // 注意边界
while (j > lo && nums[j] > pivot) {
j--;
}
while (i < hi && nums[i] <= pivot) {
i++;
}
if (i >= j) {
break;
}
swap(nums, i, j);
}
swap(nums, lo, j);
return j;
}
void shuffle(int[] nums) {
Random rand = new Random();
int n = nums.length;
for (int i = 0; i < n; i++) {
swap(nums, i, i + rand.nextInt(n - i));
}
}
void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
partition 执行的次数是二叉树节点的个数,每次执行的复杂度就是每个节点代表的子数组 nums[lo…hi] 的长度,所以总的时间复杂度就是整棵树中「数组元素」的个数。
假设数组元素个数为 N,那么二叉树每一层的元素个数之和就是 O(N);分界点分布均匀的理想情况下,树的层数为 O(logN),所以理想的总时间复杂度为 O(NlogN)。
由于快速排序没有使用任何辅助数组,所以空间复杂度就是递归堆栈的深度,也就是树高 O(logN)。
快速排序的效率存在一定随机性,如果每次 partition 切分的结果都极不均匀:
快速排序就退化成选择排序了,树高为 O(N),每层节点的元素个数从 N 开始递减,总的时间复杂度为:
N + (N - 1) + (N - 2) + … + 1 = O(N^2)
所以我们说,快速排序理想情况的时间复杂度是 O(NlogN),空间复杂度 O(logN),极端情况下的最坏时间复杂度是 O(N^2),空间复杂度是 O(N)。
力扣算法 Java 刷题笔记【快速排序】hot100(一)快速排序 快速选择算法 1
归并排序是建立在归并操作的一种高效的排序方法,该方法采用了分治的思想,比较适用于处理较大规模的数据,但比较耗内存
2022/2/22 - 8/5
class Solution {
public int[] sortArray(int[] nums) {
int[] temp = new int[nums.length];
mergeSort(nums, temp, 0, nums.length - 1);
return nums;
}
void mergeSort(int[] nums, int[] temp, int lo, int hi) {
if(lo == hi) {
return;
}
if (lo < hi) {
int middle = lo + (hi - lo) / 2;
mergeSort(nums, temp, lo, middle);
mergeSort(nums, temp, middle + 1, hi);
merge(nums, temp, middle, lo, hi);
}
}
void merge(int[] nums, int[] temp, int middle, int lo, int hi) {
int i = lo, j = middle + 1;
for (int k = lo; k <= hi; k++) {
if (i > middle) {
temp[k] = nums[j];
j++;
} else if (j > hi) {
temp[k] = nums[i];
i++;
} else if (nums[i] <= nums[j]) {
temp[k] = nums[i];
i++;
} else {
temp[k] = nums[j];
j++;
}
}
for (int p = lo; p <= hi; p++) {
nums[p] = temp[p];
}
}
}
适用处理数据量比较少或者部分有序的数据
2022/7 - 8/5
class Solution {
public int[] sortArray(int[] nums) {
for (int i = 1; i < nums.length; i++) {
insertSort(nums, i);
}
return nums;
}
void insertSort(int[] nums, int i) {
int temp = nums[i];
int j = i - 1;
for (; j >= 0 && nums[j] > temp; j--) {
nums[j + 1] = nums[j];
}
nums[j + 1] = temp;
}
}
从第一个石子开始,让它和右边相邻的石子进行比较,如果左边的石子大于右边的石子,那么就交换两个石子的位置,(也可以左小于右交换,这里采用大于交换),这样每比较一次,大的就跑到右边,直到跑到最右边
2022/8/22
class Solution {
public int[] sortArray(int[] nums) {
bubbleSort(nums);
return nums;
}
void bubbleSort(int[] nums) {
if (nums == null || nums.length < 2) {
return;
}
int temp = 0;
for (int i = 0; i < nums.length - 1; i++) {
boolean flag = true;
for (int j = 0; j < nums.length - 1 - i; j++) {
if (nums[j] > nums[j + 1]) {
temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
flag = false;
}
}
if (flag) {
break;
}
}
}
}