归并排序算法

定义

归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

解析

  1. 先将数组递归拆分成长度最大为2的小数组

  2. 拆分阶段完成

  3. 再对拆分后的子数组里面的数字进行排序,将小的移到左边,大的移到右边

  4. 这样所有的子数组都是有序的了

  5. 将这些有序的子数组进行归并

  6. 归并阶段完成

问题:如何将两个有序的集合合并成一个有序集合

只需要将两个集合同时进行一次便利即可完成,比较当前集合中最小的元素,将最小的元素放入新的集合中,这样就得到最后的有序集合

图片分析

 

代码示例

package com.example.activiti.algorithm;
​
import java.util.Arrays;
​
/**
 * 归并排序
 */
public class MergeSort {
    private static int[] array = {10,4,6,3,8,2,5,7,1,9};
​
    public static void main(String[] args) {
        long beginTime = System.currentTimeMillis();
        //归并排序
        int[] resultArray = mergeSort(array);
        long endTime = System.currentTimeMillis();
        System.out.println("耗时=" + (endTime - beginTime) + " | " + Arrays.toString(resultArray));
    }
​
    /**
     * 归并排序
     * @param source
     * @return
     */
    private static int[] mergeSort(int[] source) {
        //数组长度
        int sourceLen = source.length;
        //如果长度大于2就拆分
        if(sourceLen > 2){
            //计算中间分割的角标位置
            int midIndex = sourceLen / 2;
            //对分割位置左边的数组进行递归拆分知道数组长度<=2
            int[] leftArray = mergeSort(Arrays.copyOf(source,midIndex));
            //对分割位置右边的数组进行递归拆分知道数组长度<=2
            int[] rightArray = mergeSort(Arrays.copyOfRange(source,midIndex,sourceLen));
            System.out.println(Arrays.toString(leftArray ) + "|" + Arrays.toString(rightArray));
            //上面的递归已经将左右两边的子数组排好序,将两个有序的数组合并为一个数组
            int[] mergeArray = mergeArrays(leftArray,rightArray);
            System.out.println("合并的数组:" + Arrays.toString(mergeArray));
            return mergeArray;
        }
        // 否则说明集合中只有一个或者两个元素,可以进行这两个元素的比较排序了
        else{
            // 如果条件成立,说明数组中只有一个元素,或者是数组中的元素都已经排列好位置了
            if(sourceLen == 1 || source[0] <= source[1]) {
                return source;
            } else {
                //交换左右的值,也就是对子数组中的值排序
                int targetp[] = new int[sourceLen];
                targetp[0] = source[1];
                targetp[1] = source[0];
                return targetp;
            }
        }
    }
​
    /**
     * 合并两个有序集合
     * @param leftArray
     * @param rightArray
     * @return
     */
    private static int[] mergeArrays(int[] leftArray, int[] rightArray) {
        //定义一个新的容纳两个子数组的新数组
        int[] finalArray = new int[leftArray.length + rightArray.length];
        //左边子数组的长度
        int leftArrayLength = leftArray.length;
        //右边子数组的长度
        int rightArrayLength = rightArray.length;
        //合并后新数组的长度
        int finalArrayLength = finalArray.length;
        //只需要以新数组的长度便利一次即可
        for(int index = 0,leftIndex = 0,rightIndex = 0;index < finalArrayLength;index++){
            //获取左边集合当前角标上的值
            int leftValue = leftIndex >= leftArrayLength?Integer.MAX_VALUE:leftArray[leftIndex];
            //获取右边集合当前角标上的值
            int rightValue = rightIndex >= rightArrayLength?Integer.MAX_VALUE:rightArray[rightIndex];
            //如果左边数组角标上的值<右边数组角标上的值,那么新数组就是左边数组角标上的值
            if(leftValue < rightValue){
                leftIndex++;
                finalArray[index] = leftValue;
            }
            //否则就取右边数组角标上的值
            else{
                rightIndex++;
                finalArray[index] = rightValue;
            }
        }
        return finalArray;
    }
}

结果

[6]|[3, 8]
合并的数组:[3, 6, 8]
[4, 10]|[3, 6, 8]
合并的数组:[3, 4, 6, 8, 10]
[7]|[1, 9]
合并的数组:[1, 7, 9]
[2, 5]|[1, 7, 9]
合并的数组:[1, 2, 5, 7, 9]
[3, 4, 6, 8, 10]|[1, 2, 5, 7, 9]
合并的数组:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
耗时=0 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Fork/Join运行归并算法

如果数据量不大,采用上面的归并算法速度会不错,但是如果数据量达到亿级,效率大打折扣,这是可以使用Fork/Join来优化执行性能

接着我们可以使用Fork/Join框架来优化归并算法的执行性能,将拆分后的子任务实例化成多个ForkJoinTask任务放入待执行队列,并由Fork/Join框架在多个ForkJoinWorkerThread线程间调度这些任务。如下图所示:

 

Fork/Join加入后的代码

package com.example.activiti.algorithm;
​
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
​
/**
 * Fork/Join框架优化归并算法
 */
public class ForkJoinMergeSort {
    private static int[] array = {10,4,6,3,8,2,5,7,1,9};
​
    public static void main(String[] args) {
        try {
            long beginTime = System.currentTimeMillis();
            ForkJoinPool pool = new ForkJoinPool();
            MergeSortTask task = new MergeSortTask(array);
            ForkJoinTask<int[]> taskResult = pool.submit(task);
            int[] resultArray = taskResult.get();
            long endTime = System.currentTimeMillis();
            System.out.println("耗时=" + (endTime - beginTime) + " | " + Arrays.toString(resultArray));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.example.activiti.algorithm;
​
import java.util.Arrays;
import java.util.concurrent.RecursiveTask;
​
/**
 * 有结果的任务
 */
public class MergeSortTask extends RecursiveTask<int[]> {
    private int source[];
​
    public MergeSortTask(int[] source) {
        this.source = source;
    }
​
    @Override
    protected int[] compute() {
        //数组长度
        int sourceLen = source.length;
        //如果长度大于2就拆分
        if(sourceLen > 2){
            //计算中间分割的角标位置
            int midIndex = sourceLen / 2;
            //拆分子任务一   数组长度<=2
            MergeSortTask leftTask = new MergeSortTask(Arrays.copyOf(source, midIndex));
            leftTask.fork();
            //拆分子任务二   数组长度<=2
            MergeSortTask rightTask = new MergeSortTask(Arrays.copyOfRange(source,midIndex,sourceLen));
            rightTask.fork();
            //左边子数组
            int[] leftArray = leftTask.join();
            //右边子数组
            int[] rightArray = rightTask.join();
            //上面的递归已经将左右两边的子数组排好序,将两个有序的数组合并为一个数组
            int[] mergeArray = mergeArrays(leftArray,rightArray);
            System.out.println("合并的数组:" + Arrays.toString(mergeArray));
            return mergeArray;
        }
        // 否则说明集合中只有一个或者两个元素,可以进行这两个元素的比较排序了
        else{
            // 如果条件成立,说明数组中只有一个元素,或者是数组中的元素都已经排列好位置了
            if(sourceLen == 1 || source[0] <= source[1]) {
                return source;
            } else {
                //交换左右的值,也就是对子数组中的值排序
                int targetp[] = new int[sourceLen];
                targetp[0] = source[1];
                targetp[1] = source[0];
                return targetp;
            }
        }
    }
​
    /**
     * 合并两个有序集合
     * @param leftArray
     * @param rightArray
     * @return
     */
    private static int[] mergeArrays(int[] leftArray, int[] rightArray) {
        //定义一个新的容纳两个子数组的新数组
        int[] finalArray = new int[leftArray.length + rightArray.length];
        //左边子数组的长度
        int leftArrayLength = leftArray.length;
        //右边子数组的长度
        int rightArrayLength = rightArray.length;
        //合并后新数组的长度
        int finalArrayLength = finalArray.length;
        //只需要以新数组的长度便利一次即可
        for(int index = 0,leftIndex = 0,rightIndex = 0;index < finalArrayLength;index++){
            //获取左边集合当前角标上的值
            int leftValue = leftIndex >= leftArrayLength?Integer.MAX_VALUE:leftArray[leftIndex];
            //获取右边集合当前角标上的值
            int rightValue = rightIndex >= rightArrayLength?Integer.MAX_VALUE:rightArray[rightIndex];
            //如果左边数组角标上的值<右边数组角标上的值,那么新数组就是左边数组角标上的值
            if(leftValue < rightValue){
                leftIndex++;
                finalArray[index] = leftValue;
            }
            //否则就取右边数组角标上的值
            else{
                rightIndex++;
                finalArray[index] = rightValue;
            }
        }
        return finalArray;
    }
}

结果

合并的数组:[3, 6, 8]
合并的数组:[3, 4, 6, 8, 10]
合并的数组:[1, 7, 9]
合并的数组:[1, 2, 5, 7, 9]
合并的数组:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
耗时=9 | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值