所谓成长就是在人世间这个道场里的一场自我救赎与修行
Java中常用的排序工具类是Arrays.sort()和Collections.sort(),它们底层使用的是基于快速排序(quicksort)算法和归并排序(mergesort)算法的排序算法。
对于Arrays.sort()方法,其底层使用的是经过优化的快速排序算法,它是一种基于分治思想的排序算法,通过不断地把待排序的数组分割成较小的子数组进行递归排序,最终将这些子数组合并起来得到有序的结果。快速排序算法的时间复杂度为O(nlogn),它是目前使用最广泛的排序算法之一。
对于Collections.sort()方法,其底层使用的是归并排序算法,它也是一种基于分治思想的排序算法,它将待排序的数组不断划分成更小的子数组,对每个子数组进行排序后再将它们合并成一个有序的数组。归并排序算法的时间复杂度为O(nlogn),它具有稳定性和适用于大规模数据排序的优点。
需要注意的是,对于小规模数据排序,Arrays.sort()方法采用了插入排序(insertion sort)和选择排序(selection sort)的混合算法进行排序,而Collections.sort()方法则直接使用归并排序算法进行排序。
没想到吧, 设计模式竟然就在我们身边, 通过将各种排序进行组合, 得到在各个数据量中的优化解,
其实快速在小数据量的情况下, 并不是很快, 如果使用其他排序算法的情况下, 这个时候就要考虑排序算法的时间复杂度, 空间复杂度了
这个时候加上了我们的插入排序跟我们的选择排序, 简直是妙呀
Arrays.sort()方法采用了一种优化的排序算法,对于小规模数据使用了插入排序和选择排序的混合算法进行排序,其源码如下:
private static void sort(int[] a, int left, int right, int[] work, int workBase, int workLen) {
// 当数组长度小于某个阈值时,使用插入排序或选择排序进行排序
if (right - left < INSERTION_SORT_THRESHOLD) {
insertionSort(a, left, right);
return;
} else if (right - left < QUICKSORT_THRESHOLD) {
binarySort(a, left, right, left + countRunAndMakeAscending(a, left, right));
return;
}
// 继续使用快速排序
int pivot = medianOfThree(a, left, left + ((right - left) >> 1), right);
int i = left;
int j = right;
int p = left;
int q = right;
while (true) {
if (i <= j) {
if (i != j) {
if ((j - i) != 1) {
swap(a, i, pivot);
}
if (a[i] <= pivot) {
i++;
continue;
}
swap(a, i, j);
}
j--;
}
while (a[j] > pivot) {
j--;
}
while (i < j && a[i] <= pivot) {
i++;
}
if (i < j) {
swap(a, i, j);
} else {
break;
}
}
// 将相等的元素移到数组中间
int k = Math.min(i - left, p - left);
vecswap(a, left, i - k, k);
k = Math.min(q - j, right - j - 1);
vecswap(a, i, right - k, k);
// 对剩下的元素继续递归排序
if ((k = i - left) > 1) {
sort(a, left, i - 1, work, workBase, workLen);
}
if ((k = right - j) > 1) {
sort(a, j + 1, right, work, workBase, workLen);
}
}
可以看到,当待排序的数组长度小于某个阈值(默认为47)时,使用插入排序或选择排序进行排序,否则使用快速排序算法进行排序。
具体来说,当待排序的数组长度小于15时,使用插入排序进行排序;当待排序的数组长度大于等于15且小于47时,使用选择排序进行排序。选择排序的优点是不需要额外的空间来存储排序结果,且由于每次选择最小(或最大)的元素交换到数组的最前面,可以减少后续排序的比较次数。
需要注意的是,虽然插入排序和选择排序在时间复杂度上不如快速排序,但是对于小规模数据排序它们的时间复杂度较低,并且在实际应用中它们的表现可能比快速排序更好。
归并排序是一种经典的排序算法,它基于分治思想,将待排序的数组分成两个子数组,对每个子数组进行排序后再将它们合并成一个有序的数组。
下面是归并排序的详细实现步骤:
分解数组:将待排序的数组从中间分成两个子数组,直到不能再分解为止。
排序子数组:对每个子数组进行排序,可以使用递归或非递归的方式。
合并数组:将排好序的子数组合并成一个有序的数组。
具体的实现方法可以是:
private static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
// 1. 分解数组
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 2. 排序子数组
merge(arr, left, mid, right);
}
}
private static void merge(int[] arr, int left, int mid, int right) {
// 定义左右两个子数组的开始和结束位置
int leftStart = left;
int leftEnd = mid;
int rightStart = mid + 1;
int rightEnd = right;
// 计算左右两个子数组的长度
int leftLength = leftEnd - leftStart + 1;
int rightLength = rightEnd - rightStart + 1;
// 创建左右两个子数组的临时数组
int[] leftTemp = new int[leftLength];
int[] rightTemp = new int[rightLength];
// 将左右两个子数组复制到临时数组中
for (int i = 0; i < leftLength; i++) {
leftTemp[i] = arr[leftStart + i];
}
for (int j = 0; j < rightLength; j++) {
rightTemp[j] = arr[rightStart + j];
}
// 3. 合并数组
int i = 0;
int j = 0;
int k = leftStart;
while (i < leftLength && j < rightLength) {
if (leftTemp[i] < rightTemp[j]) {
arr[k++] = leftTemp[i++];
} else {
arr[k++] = rightTemp[j++];
}
}
// 将左右两个子数组中剩余的元素复制到原数组中
while (i < leftLength) {
arr[k++] = leftTemp[i++];
}
while (j < rightLength) {
arr[k++] = rightTemp[j++];
}
}
在实现过程中,需要注意以下几点:
在合并数组时,需要使用额外的空间来存储左右两个子数组的临时数组。
在合并数组时,需要比较左右两个子数组中的元素,并将较小的元素先放入原数组中。
在合并数组时,需要将左右两个子数组中剩余的元素复制到原数组中。
在递归排序时,需要确定分解数组的位置,通常可以使用左右指针的方式来实现。
归并排序的时间复杂度为O(nlogn),它具有稳定性和适用于大规模数据排序的优点。但是,它需要额外的空间来存储临时数组,因此在实际应用中需要考虑内存的消耗。