1、介绍
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子集合合并,得到完全有序的集合;即先使每个子集合有序,再使子集合间有序。若将两个有序集合合并成一个有序集合,称为 2-路归并。
2、分析
- 把长度为 n 的输入集合,分成两个长度为 n/2 的子集合;
- 对这两个子集合分别采用归并排序;
- 将两个排序好的子集合合并成一个最终的排序集合。
左边排好序 + 右边排好序 + merge 让整体有序
3、实现
3.1、递归
/**
* @description: 归并排序-递归实现
* @author: erlang
* @since: 2020-09-28 22:31
*/
public class MergeSort {
/**
* 递归方法排序
*
* @param arr 待排序数组
*/
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
/**
* @param arr 待处理数组
* @param left 左边界
* @param right 有边界
*/
private static void process(int[] arr, int left, int right) {
if (left == right) {
return;
}
int mid = left + ((right - left) >> 1);
process(arr, left, mid);
process(arr, mid + 1, right);
merge(arr, left, mid, right);
}
/**
* 合并
*
* @param arr 带处理数组
* @param left 左边界
* @param mid 中间分界线
* @param right 右边界
*/
public static void merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int index = 0;
int p1 = left;
int p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 把 [left, mid] 或 [mid + 1, right] 中剩余的元素存到 help 数组中
while (p1 <= mid) {
help[index++] = arr[p1++];
}
while (p2 <= right) {
help[index++] = arr[p2++];
}
// 将 help 中的元素重新放回 arr 的 [left, right] 区间内
for (int i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
}
public static void main(String[] args) {
ArraySortUtils.testSort(MergeSort::mergeSort, 100, 100);
}
}
3.2、迭代
/**
* @description: 归并排序-迭代实现
* @author: erlang
* @since: 2020-09-28 22:55
*/
public class MergeSort {
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
// 当前有序的,左组长度
int mergeSize = 1;
while (mergeSize < n) {
int left = 0;
while (left < n) {
int mid = left + mergeSize - 1;
if (mid >= n) {
break;
}
int right = Math.min(mid + mergeSize, n - 1);
merge(arr, left, mid, right);
left = right + 1;
}
// 如果 mergeSize 大于数组长度的二分之一,说明数组已经排完序了
// 防止 mergeSize << 1 超过 int 的长度
if (mergeSize > (n >> 1)) {
break;
}
// 每次增加一倍
mergeSize <<= 1;
}
}
/**
* 合并
*
* @param arr 带处理数组
* @param left 左边界
* @param mid 中间分界线
* @param right 右边界
*/
public static void merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int index = 0;
int p1 = left;
int p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[index++] = arr[p1++];
}
while (p2 <= right) {
help[index++] = arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
}
public static void main(String[] args) {
ArraySortUtils.testSort(MergeSort::mergeSort, 100, 100);
}
}
4、实战
4.1、小数和
4.1.1、描述
在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。
例子: [1,3,4,2,5]
- 1左边比1小的数:没有
- 3左边比3小的数:1
- 4左边比4小的数:1、3
- 2左边比2小的数:1
- 5左边比5小的数:1、3、4、 2
- 所以数组的小和为1+1+3+1+1+3+4+2=16
4.1.2、实现
/**
* @description: 小和
* @author: erlang
* @since: 2020-10-13 21:49
*/
public class SmallSum {
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process(arr, 0, arr.length - 1);
}
private static int process(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return process(arr, left, mid)
+
process(arr, mid + 1, right)
+
merge(arr, left, mid, right);
}
private static int merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int index = 0;
int p1 = left;
int p2 = mid + 1;
int sum = 0;
while (p1 <= mid && p2 <= right) {
sum += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0;
help[index++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[index++] = arr[p1++];
}
while (p2 <= right) {
help[index++] = arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
return sum;
}
// for test
public static int comparator(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int res = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < i; j++) {
res += arr[j] < arr[i] ? arr[j] : 0;
}
}
return res;
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 10;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = ArraySortUtils.generateRandomArray(maxSize, maxValue);
int[] arr2 = ArraySortUtils.copyArray(arr1);
int smallSum = smallSum(arr1);
int comparator = comparator(arr2);
if (smallSum != comparator) {
System.out.println("smallSum ==> " + smallSum);
System.out.println("comparator ==> " + comparator);
succeed = false;
ArraySortUtils.printArray(arr1);
ArraySortUtils.printArray(arr2);
break;
}
}
System.out.println(succeed ? "Nice!" : "Fail!");
}
}
4.2、数组中的逆序对
4.2.1、描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
4.2.2、实现
class Solution {
public int reversePairs(int[] nums) {
if (nums == null || nums.length < 2) {
return 0;
}
return process(nums, 0, nums.length - 1);
}
private static int process(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return process(arr, left, mid)
+
process(arr, mid + 1, right)
+
merge(arr, left, mid, right);
}
private static int merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int index = 0;
int p1 = left;
int p2 = mid + 1;
int sum = 0;
while (p1 <= mid && p2 <= right) {
sum += arr[p1] > arr[p2] ? (right - p2 + 1) : 0;
help[index++] = arr[p1] > arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[index++] = arr[p1++];
}
while (p2 <= right) {
help[index++] = arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
return sum;
}
}
5、工具类
5.1、MergeSortUtils
/**
* @description: 归并排序工具类
* @author: erlang
* @since: 2020-10-12 22:32
*/
public class MergeSortUtils {
/**
* 合并
*
* @param arr 带处理数组
* @param left 左边界
* @param mid 中间分界线
* @param right 右边界
*/
public static void merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int index = 0;
int p1 = left;
int p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[index++] = arr[p1++];
}
while (p2 <= right) {
help[index++] = arr[p2++];
}
for (int i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
}
}
5.2、ArraySortUtils
import java.util.Arrays;
import java.util.function.Consumer;
/**
* @description: 数组排序工具
* @author: erlang
* @since: 2020-08-29 10:49
*/
public class ArraySortUtils {
/**
* 随机生成待测试的数组
*
* @param maxSize 数组最大长度
* @param maxValue 数组中最大的值
* @return 返回生成的数组
*/
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
/**
* 输出数组的元素
*
* @param arr 数组
*/
public static void printArray(int[] arr) {
System.out.println("");
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++START");
for (int value : arr) {
System.out.print(value + " ");
}
System.out.println("");
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++END");
}
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
/**
* 重新复制一个新数组
*
* @param arr 待复制的数组
* @return 返回复制的新数组
*/
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
System.arraycopy(arr, 0, res, 0, arr.length);
return res;
}
/**
* 比较两个数组的元素是否相等
*
* @param arr1 待比较的数组 1
* @param arr2 待比较的数组 2
* @return 返回校验结果 true/false
*/
public static boolean isEqual(int[] arr1, int[] arr2) {
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1 == null || arr2 == null) {
return false;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
/**
* 测试排序方法的排序结果是否正确
*
* @param consumer 回调用户自定义的排序方法
* @param maxSize 数组最大长度
* @param maxValue 数组中最大的数,即 0 - maxValue
*/
public static void testSort(Consumer<int[]> consumer, int maxSize, int maxValue) {
int testTime = 5000;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
// 生成新数组
int[] arr1 = ArraySortUtils.generateRandomArray(maxSize, maxValue);
// 复制新数组
int[] arr2 = copyArray(arr1);
// 回调用户自定义的排序方法,对 arri1 排序
consumer.accept(arr1);
// 使用系统自带的排序方法,对 arr2 排序
comparator(arr2);
// 比较两种处理结果是否一致
if (!isEqual(arr1, arr2)) {
succeed = false;
printArray(arr1);
printArray(arr2);
break;
}
}
// 输出测试结果
System.out.println(succeed ? "Nice!" : "Fail!");
}
}