归并排序(Merge Sort)是一种基于分治法的排序算法,时间复杂度为O(n log n),空间复杂度为O(n)。它通过递归地将数组分成两个子数组,分别排序,然后合并成有序数组。
归并排序算法原理:
- 分(Divide):将待排序数组递归地分成两半,直到每个子数组只有一个元素,单个元素天然有序。
- 治(Conquer):对每个子数组进行排序。
- 合(Merge):将两个已排序的子数组合并成一个有序数组。
算法步骤:
- 如果数组长度小于2,无需排序,直接返回。
- 找到数组的中点,将数组分成左右两部分。
- 递归地对左半部分和右半部分进行归并排序。
- 合并两个有序子数组:使用两个临时数组存储左右子数组,比较两个临时数组的元素,依次将较小的元素放入原数组,如果有剩余元素,将剩余元素复制到原数组。
Java 代码实现:
public void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
private void merge(int[] arr, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int[] leftArr = new int[n1];
int[] rightArr = new int[n2];
// 填充临时数组
// 将原数组的左子数组(arr[left..mid])复制到 leftArr。
for (int i = 0; i < n1; i++) {
leftArr[i] = arr[left + i];
}
// 将原数组的右子数组(arr[mid+1..right])复制到 rightArr。
for (int j = 0; j < n2; j++) {
rightArr[j] = arr[mid + 1 + j];
}
// 合并
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) { // 使用<=保持稳定性,相同元素的相对顺序不会改变。
arr[k++] = leftArr[i++];
} else {
arr[k++] = rightArr[j++];
}
}
while (i < n1) {
arr[k++] = leftArr[i++];
}
while (j < n2) {
arr[k++] = rightArr[j++];
}
}
时间复杂度:每次递归都会二分数组,递归深度为 log n , 每次合并操作需要O(n),总共log n层,所以总时间为O(n log n)。
空间复杂度:递归是二分的,最大递归深度为 log n \log n logn,因此调用栈的空间占用为 O ( log n ) O(\log n) O(logn)。在 merge 方法中,创建了两个临时数组,总临时数组大小为 n 1 + n 2 = r i g h t − l e f t + 1 n1 + n2 = right − left + 1 n1+n2=right−left+1,在最顶层递归(合并整个数组时),right - left + 1 = n,因此临时数组的总大小最大为 O ( n ) O(n) O(n),虽然每一层递归的 merge 都会创建临时数组,但这些数组在 merge 方法结束后会被垃圾回收,且递归是分阶段执行的(先完成左子树递归,再右子树,再合并),因此不会同时存在多个大数组。总体来看,临时数组的空间占用在任何时刻不会超过 O ( n ) O(n) O(n)。其他变量空间占用为O(1),常数级别。因此总空间复杂度为: O ( n ) + O ( l o g n ) + O ( 1 ) = O ( n ) O(n)+O(logn)+O(1)=O(n) O(n)+O(logn)+O(1)=O(n),由于 O ( n ) O(n) O(n) 是主导项,归并排序的空间复杂度为 O ( n ) O(n) O(n)。
归并排序是稳定的排序算法,适用于大数据量,时间复杂度不受数据分布影响,始终为O(n log n),适合链表排序,因为只有遍历操作,没有随机插入操作。但是因为存在递归,在极大数据量的情况下可能导致栈溢出。