一、什么是归并排序
归并排序又称合并排序,它是成功应用分治技术的一个完美例子。对于一个需要排序的数组A[0..n-1],归并排序把它一分为二:A[0..n/2-1]和A[n/2..n-1],并对每个子数组递归排序,然后把这两个排好序的子数组合并为一个有序数组。下面是归并排序的例子图解:
二、单线程实现归并排序
packagecom.bob.algorithms.sort;importjava.util.Arrays;importcom.bob.algorithms.SortStrategy;/*** 归并排序
*
*@authorbob
**/
public class SingleThreadMergeSort implementsSortStrategy {public int[] sort(int[] rawArray) {
mergeSort(rawArray);returnrawArray;
}/*** 分解并合并排序,升序
*
*@paramintArr*/
private void mergeSort(int[] intArr) {if (intArr.length > 1) {//如果数组长度大于1就分解称两份
int[] leftArray = Arrays.copyOfRange(intArr, 0, intArr.length / 2);int[] rightArray = Arrays.copyOfRange(intArr, intArr.length / 2, intArr.length);
mergeSort(leftArray);
mergeSort(rightArray);//合并且排序
merge(leftArray, rightArray, intArr);
}
}/*** 合并排序
*
*@paramleftArray
*@paramrightArray
*@paramintArr*/
private void merge(int[] leftArray, int[] rightArray, int[] intArr) {//i:leftArray数组索引,j:rightArray数组索引,k:intArr数组索引
int i = 0, j = 0, k = 0;while (i < leftArray.length && j
if (leftArray[i]
intArr[k]=leftArray[i];
i++;
}else{
intArr[k]=rightArray[j];
j++;
}
k++;
}//将还剩余元素没有遍历完的数组直接追加到intArr后面
if (i ==leftArray.length) {for (; j < rightArray.length; j++, k++) {
intArr[k]=rightArray[j];
}
}else{for (; i < leftArray.length; i++, k++) {
intArr[k]=leftArray[i];
}
}
}
}
三、使用Fork/Join框架实现归并排序
Fork/Join是从JDK 1.7 加入的并发计算框架。
packagecom.bob.algorithms.sort;importjava.util.Arrays;importjava.util.concurrent.ForkJoinPool;importjava.util.concurrent.RecursiveAction;importcom.bob.algorithms.SortStrategy;public class ForkJoinMergeSort implementsSortStrategy {public int[] sort(int[] rawArray) {
ForkJoinPool pool= newForkJoinPool();
pool.invoke(newMergeSort(rawArray));returnrawArray;
}/*** 使用Fork/join的方式进行归并排序,充分利用cpu
*
*@authorzhangwensha
**/
private static class MergeSort extendsRecursiveAction {private static final long serialVersionUID = 425572392953885545L;private int[] intArr;public MergeSort(int[] intArr) {this.intArr =intArr;
}
@Overrideprotected voidcompute() {if (intArr.length > 1) {//如果数组长度大于1就分解称两份
int[] leftArray = Arrays.copyOfRange(intArr, 0, intArr.length / 2);int[] rightArray = Arrays.copyOfRange(intArr, intArr.length / 2, intArr.length);//这里分成两份执行
invokeAll(new MergeSort(leftArray), newMergeSort(rightArray));//合并且排序
merge(leftArray, rightArray, intArr);
}
}/*** 合并排序
*
*@paramleftArray
*@paramrightArray
*@paramintArr*/
private void merge(int[] leftArray, int[] rightArray, int[] intArr) {//i:leftArray数组索引,j:rightArray数组索引,k:intArr数组索引
int i = 0, j = 0, k = 0;while (i < leftArray.length && j
if (leftArray[i]
intArr[k]=leftArray[i];
i++;
}else{
intArr[k]=rightArray[j];
j++;
}
k++;
}//将还剩余元素没有遍历完的数组直接追加到intArr后面
if (i ==leftArray.length) {for (; j < rightArray.length; j++, k++) {
intArr[k]=rightArray[j];
}
}else{for (; i < leftArray.length; i++, k++) {
intArr[k]=leftArray[i];
}
}
}
}
}
四、单线程 pk 多线程
编写了舞台类,通过调整generateIntArray(10000000)的输入参数来设置待排序数组长度,试验中没有对堆容量进行设置。
packagecom.bob.algorithms;importjava.util.Arrays;importjava.util.Date;importcom.bob.algorithms.common.CommonUtil;importcom.bob.algorithms.sort.ForkJoinMergeSort;importcom.bob.algorithms.sort.SingleThreadMergeSort;/*** 舞台类,专门用来测试算法的时间
*
*@authorbob
**/
public classStage {public static voidmain(String[] args) {//变量定义
long begintime = 0;long endtime = 0;//生成排序数据
int[] rawArr = generateIntArray(10000000);int[] rawArr2 =Arrays.copyOf(rawArr, rawArr.length);
begintime= newDate().getTime();newSingleThreadMergeSort().sort(rawArr);//System.out.println(Arrays.toString(new SingleThreadMergeSort().sort(rawArr)));
endtime = newDate().getTime();
System.out.println("单线程归并排序花费时间:" + (endtime -begintime));
System.out.println("是否升序:"+CommonUtil.isSorted(rawArr, true));
begintime= newDate().getTime();newForkJoinMergeSort().sort(rawArr2);//System.out.println(Arrays.toString(new ForkJoinMergeSort().sort(rawArr2)));
endtime = newDate().getTime();
System.out.println("Fork/Join归并排序花费时间:" + (endtime -begintime));
System.out.println("是否升序:"+CommonUtil.isSorted(rawArr2, true));
}/*** 生成int类型的数组
*
*@return
*/
private static int[] generateIntArray(intlength) {int[] intArr = new int[length];for (int i = 0; i < length; i++) {
intArr[i]= new Double(Math.random() *length).intValue();
}returnintArr;
}
}
以下是数组容量在各个量级时,两种方法效率对比:
数组长度100100010000100000100000010000000
单线程 (ms)
1
2
7
33
188
2139
Fork/Join (ms)
8
9
17
63
358
1133
通过统计可以发现,当待排序序列长度较小时,使用单线程效率要高于多线程,但是随着数量不断增加,多线程执行时间越来越接近单线程的执行时间,最终在1000万这个量级开始速率远超单线程。工作中不能滥用多线程,在该使用的时候使用可以加快效率,充分利用多核。但是在不该用的时候使用徒增工作量,有可能效率还不如单线程。 感兴趣的朋友可以通过下面代码地址找到运行的全部源码自己跑跑试试看。
五、本文代码地址
包括本篇在内以后所有代码统一存放地址为:
https://github.com/mingbozhang/algorithm
六、参考
https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
《算法设计与分析基础(第3版)》