1. 基本思想
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
分而治之
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
2. 合并相邻有序子序列
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
3. 代码实现
递归方式
package com.algorithm;
import java.util.Arrays;
import java.util.Random;
/**
* Created by lizq on 2020/3/1.
*/
enum TYPE {
MOVE, NEW
}
public class MergeSort {
public static void main(String[] args) {
long[] arrs = RandomArr.createLongArr(20, 0, 200);
long[] arrs2 = Arrays.copyOf(arrs, arrs.length);
Arrays.stream(arrs).forEach(i -> {
System.out.print(i + " ");
});
System.out.println();
System.out.println("mergeMove");
sort(arrs, 0, arrs.length - 1, TYPE.MOVE);
Arrays.stream(arrs).forEach(i -> {
System.out.print(i + " ");
});
System.out.println();
System.out.println("mergeNewArr");
sort(arrs2, 0, arrs2.length - 1, TYPE.NEW);
Arrays.stream(arrs2).forEach(i -> {
System.out.print(i + " ");
});
System.out.println();
}
/**
* 使用递归方式进行排序
* @param arr
* @param from
* @param to
* @param type
*/
public static void sort(long[] arr, int from, int to, TYPE type) {
if (to - from < 2) {
if (arr[from] > arr[to]) {
long tmp = arr[from];
arr[from] = arr[to];
arr[to] = tmp;
}
} else {
int m = (from + to) / 2;
sort(arr, from, m, type);
sort(arr, m + 1, to, type);
if (TYPE.MOVE == type) {
mergeMove(arr, from, to, m + 1);
} else if (type.NEW == type) {
mergeNewArr(arr, from, to, m + 1);
}
}
}
/**
* 不创建新的数组,但是会频繁移动数据,增加时空复杂度
*
* @param arr
* @param from
* @param to
*/
public static void mergeMove(long[] arr, int from, int to, int r) {
for (int i = from; i < to && r <= to; i++) {
if (arr[i] > arr[r]) {
long tmp = arr[r];
for (int j = r; j > i; j--) {
arr[j] = arr[j - 1];
}
arr[i] = tmp;
r++;
}
}
}
/**
* 创建新的数组,进行数据暂存
* @param arr
* @param from
* @param to
* @param r
*/
public static void mergeNewArr(long[] arr, int from, int to, int r) {
long[] lArr = new long[r - from];
System.arraycopy(arr, from, lArr, 0, r - from);
long[] rArr = new long[to - r + 1];
System.arraycopy(arr, r, rArr, 0, to - r + 1);
for (int i = 0, j = 0, k = from; i < lArr.length && j < rArr.length; k++) {
if (lArr[i] > rArr[j]) {
arr[k] = rArr[j];
j++;
} else {
arr[k] = lArr[i];
i++;
}
if (i == lArr.length) {
System.arraycopy(rArr, j, arr, ++k, rArr.length - j);
break;
}
if (j == rArr.length) {
System.arraycopy(lArr, i, arr, ++k, lArr.length - i);
break;
}
}
}
}
forkJoin方式
package ThreadTest.demo;
import lombok.Data;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.*;
/**
* Created by lizq on 2020/3/1.
*/
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long[] arrs = RandomArr.createLongArr(30, 0, 100);
Arrays.stream(arrs).forEach(i -> {
System.out.print(i + " ");
});
long rlt = Arrays.stream(arrs).sum();
System.out.println("sum " + rlt);
// ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
ForkJoinPool forkJoinPool = new ForkJoinPool(5);
// ForkJoinTask<Long> forkJoinTask = forkJoinPool.submit(new SumTask(arrs, 0, arrs.length - 1));
// rlt = forkJoinTask.get();
// System.out.println("sum " + rlt);
ForkJoinTask forkJoinTask = forkJoinPool.submit(new orderTask(arrs, 0, arrs.length - 1));
forkJoinTask.get();
Arrays.stream(arrs).forEach(i -> {
System.out.print(i + " ");
});
}
}
class orderTask extends RecursiveAction {
private long[] arr;
private int from;
private int to;
public orderTask(long[] arr, int from, int to) {
this.arr = arr;
this.from = from;
this.to = to;
}
@Override
protected void compute() {
// System.out.print("thread begin : " + Thread.currentThread().getName() + " from= " + from + "; to=" + to);
if (to - from < 2) {
if (arr[from] > arr[to]) {
long tmp = this.arr[from];
this.arr[from] = this.arr[to];
this.arr[to] = tmp;
}
} else {
int m = (from + to) / 2;
orderTask leftSum = new orderTask(this.arr, from, m);
orderTask rightSum = new orderTask(this.arr, m + 1, to);
leftSum.fork();
rightSum.fork();
leftSum.join();
rightSum.join();
// 组合排序
for (int l = from, r = m + 1; l < to && r <= to; l++) {
if (arr[l] > arr[r]) {
long tmp = arr[r];
for (int i = r; i > l; ) {
arr[i] = arr[--i];
}
arr[l] = tmp;
r++;
}
}
}
// System.out.print("thread end : " + Thread.currentThread().getName() + " from= " + from + "; to=" + to);
}
}
class RandomArr {
public static long[] createLongArr(int length, int low, int high) {
if (length < 1) {
throw new RuntimeException("length < 1");
}
return new Random().longs(low, high).limit(length).toArray();
}
}
4. 总结
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。