归并排序
问题描述:归并排序是常见排序算法中的一种,主要用于将一串长度为 n 序列按一定规则排序。
核心算法: 分治、 归并
解题思路:将数组分成两个子数组,递归地对子数组排序,最后将排序后的子数组合并(归并)成一个有序数组。
注:本文中归并排序默认解析为最常见的二分归并排序。
算法设计
初始化
首先用 l 和 r 作为左右指针分别标记待排序数组的首尾, mid 指针标记待排序数组中心有
向下递归
将数组按 l ~ mid 和 mid + 1 ~ r 拆分成两个数组,并继续向下递归拆分,直到拆分的数组长度为 1。
向上合并:
对于两个已排序的数组用 i、j分别标记两个子数组的首元素,并切换式遍历两个子数组,按排序规则合成新数组。
注:切换式遍历指的是对 i、j 双指针同时遍历,按排序规则将不断对比双指针元素,将优先级高的元素优先方法新数组中。
算法分析
时间复杂度:每次分割数组的时间复杂度为 O(1),而每次合并数组的时间复杂度为 O(n)。
![](https://i-blog.csdnimg.cn/blog_migrate/497f66ef488cb2dce81a914ef98f266a.jpeg)
注:证明参考《数据结构、算法与应用 C++语言描述原书第二版》
代码实现
注意:输入需要自行根据题目更改
public class Main {
public static void main(String[] args) {
int[] arr = new int[]{-1, 3, 2, 9, 9, 10, 123, 9, 87, 1, -10, -1, -200};
mergeSort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
static void mergeSort(int[] arr, int l, int r) {
int len = r - l + 1;
if (len <= 1) {
return;
}
int mid = (l + r) / 2;
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
static void merge(int[] arr, int l, int mid, int r) {
int[] newArr = new int[r - l + 1];
int i = l, j = mid + 1, k = 0;
while (i <= mid && j <= r) {
if (arr[i] > arr[j]) {
newArr[k] = arr[j++];
} else {
newArr[k] = arr[i++];
}
k++;
}
while (i <= mid) {
newArr[k++] = arr[i++];
}
while (j <= r) {
newArr[k++] = arr[j++];
}
i = l;
for (int g = 0; g < k; g++) {
arr[i++] = newArr[g];
}
}
}
经典拓展:逆序数
![](https://i-blog.csdnimg.cn/blog_migrate/637eb9a00889dccd17fb2b9d4df22f3f.png)
基本思路
分解:与归并排序一样,将大数组分解成两个小数组,直到数组长度为 1 或 2时,可直接计算出逆序数。
归并:归并排序中归并的过程,实质就是消除逆序对的过程,根据这个实质,我们可以计算消除的逆序对数量,具体如下:
1. 若前子数组元素被选中,则没有消除逆序对
2. 若后子数组元素被选中,则前子数组还未被选中的元素都将与该元素构造逆序对,即选中该元素所消除的逆序对数为当前前子数值未被选中的元素个数。
综上,根据上述两种情况,每次合并消除的逆序数累加,则为该次合并出现的逆序数个数,再加上两个子数组本身的逆序数个数,则为合并后整个数组的逆序数总数。