归并排序以及基于ForkJoin框架实现的并行归并排序(使用RecursiveAction)

1. 首先理解归并排序的实现过程:

我想大家肯定都了解过归并排序,那些文字描述包括分治法我就不复制粘贴了,我这里以图的方式给出它的原理

归并排序以及基于ForkJoin框架实现的并行归并排序(使用RecursiveAction)_第1张图片

重要点:

(1):假设a数组存储待排序数组,在合并数组时避免不了改变数组内的值,所以需要一个t数组暂时保存中间结果,举个例子:在合并数组为【5,4】时,合并的结果应该是【4,5】,先将结果保存在t里面(因为中间不是简单的交换,可能还包含多个步骤),最后再用t数组中的【4,5】替换掉a数组中的【5,4】;

(2):所说的合并到底指的是什么?

合并指的是将一个数组中的两个连在一起的子数组按照原先的下标有序的组合在一起,举个例子:此时数组为5 4 3 2 1,假设现在要合并子数组为【0,0】和【1,1】,那么合并完成后就是4 5 3 2 1;

如何实现合并?(下面的代码拿着笔随便举个例子run一遍就会明白了,这也是合并排序的核心

void Merge(int c[], int d[], int l, int m, int r){		//合并两个连续的子数组
	int i = l, j = m+1, k = l ;

	while ( ((i<=m) && (j<=r)) ){
		if( c[i]

剩下的代码其实都很简单了:

先是递归程序:

void mergeSort(int a[], int left, int right){
	int s ;
	if(left < right){

		int i = (left+right)/2 ;

		mergeSort(a, left, i) ;
		mergeSort(a, i+1, right) ;
		
		Merge(a, d, left, i, right) ;    //d数组是全局变量
		
		Copy(a, d, left, right) ;		
	}
}

然后每当合并后执行Copy:

void Copy(int a[],int b[],int left,int right){    //将a数组的[low, right]段替换成b段的,其余保持不变  
    for(int i = left; i <= right; i++)      
        a[i] = b[i];  
}  

2. 并行的归并排序:

当数组很大时,执行归并排序是很耗费时间和空间的,因为要拆分数组到每一个数,所以很麻烦,基于这样的问题提出并行解决,当递归执行mergeSort()时,我们创建一个新的线程,让线程来为我们做这个事情,当这个线程执行到下一层递归时在创建一个线程,设置好线程的同步点,这样就可以达到并行求解的目的,那么如何实现?

结合前面的分析,我们需要在线程中创建线程来达到递归的目的:那么这个线程做什么呢?实际上做的是递归程序做的事情,也就是mergeSort()函数,那么相关的变量就是两个数组、left和right了。

此时只有一个问题需要分析:线程的同步点。

在一开始的图中,串行求解的流程是按照DFS(深度优先遍历)来进行的,也就是走到树的一个分支的尽头后再走下一个分支。但并行的应该是各自的线程走各自的分支,也就是BFS(广度优先遍历),各走各的无所谓,但每一条路应该是走到尽头再返回,而这个同步点到底是什么呢?我画图来说明:

归并排序以及基于ForkJoin框架实现的并行归并排序(使用RecursiveAction)_第2张图片

如何实现线程同步?可以使用join()方法:

    MSort M1=new MSort(a,temp,low,mid);
    MSort M2=new MSort(a,temp,mid+1,high);
    M1.start();
    M2.start();
    M1.join();
    M2.join();

注意:join()方法不能在start()方法的前面执行。

所有问题都解决了,代码如下(与前面的不太一样,但大体思路不变,因为编写两个程序的时间跨度有点久):

public class MSort extends Thread{
    private int a[];
    private int temp[];
    private int low;
    private int high;
    public MSort(int arr[],int t[],int lo,int hi){
        a=arr;
        temp=t;
        low=lo;
        high=hi;
    }
    void mergesort()throws Exception{
        int mid=(low+high)/2;
        if(low==high) return ;
        try{
            MSort M1=new MSort(a,temp,low,mid);
            MSort M2=new MSort(a,temp,mid+1,high);
            M1.start();
            M2.start();

            M1.join();
            M2.join();
        }
        catch(NullPointerException e){
            System.err.println("Exception is"+e.toString());
        }
        catch(InterruptedException e){
            System.err.println("Exception is"+e.toString());
        }

        //相当于copy数组
        for(int i=low;i<=high;i++)
            temp[i]=a[i];

        int i = low, k = low;
        int j = mid+1;

        //合并数组
        while ((i <= mid) && (j <= high)){
            if (temp[i] < temp[j]){
                a[k++] = temp[i++];
            }
            else if (temp[i] > temp[j]){
                a[k++] = temp[j++];
            }
        }
        while (i <= mid)
            a[k++] = temp[i++];
        while (j <= high)
            a[k++] = temp[j++];
    }
    public void run (){
        try{
            System.out.println( "*************" + Thread.currentThread().getName());
            mergesort();}
        catch(Exception e){
            System.err.println("Exception"+e.toString());
        }
    }

    public static void main(String[] args) throws Exception {
        int[] arr = new int[10000];
        for(int i=0; i

运行试试:

归并排序以及基于ForkJoin框架实现的并行归并排序(使用RecursiveAction)_第3张图片

好像是堆栈内存不够用了0.0,调小一点:

归并排序以及基于ForkJoin框架实现的并行归并排序(使用RecursiveAction)_第4张图片

3. 使用ForkJoin框架的归并排序:

由于上面那个并行归并效率实在是差劲,所以使用了递归求解的并行框架ForkJoin,这个框架里面有管理线程的ForkJoinPool线程池,所以用起来不仅方便最重要的是运行效率高了不少,而且不可能发生内存不够的异常,实现其实代码和上面的差不多,只是使用了一点陌生的类和方法,百度百度就可以解决,代码如下:

import java.util.concurrent.RecursiveAction;

public class MsortAction extends RecursiveAction {
    private int a[];
    private int t[];
    private int l;      //数组最小下标
    private int r;      //数组最大下标

    @Override
    protected void compute() {
        if (l == r) {
            return;
        }
        int m = (l+r)/2;

        MsortAction msortTask1 = new MsortAction(a, t, l, m);
        MsortAction msortTask2 = new MsortAction(a, t, m+1, r);
        invokeAll(msortTask1, msortTask2);

        merge(a, t, l, m, r);
        copy(a, t, l, r);
    }

    /**
     * 合并a中的两个连续子数组到t
     * @param a
     * @param t
     * @param l:第一个子数组的开始下标
     * @param m:第一个子数组的结束下标
     * @param r:第二个子数组的结束下标
     */
    protected void merge(int[] a, int[] t, int l, int m, int r) {
        int i = l;
        int k = l;
        int j = m+1;

        //合并数组
        while ((i <= m) && (j <= r)){
            if (a[i] < a[j]){
                t[k++] = a[i++];
            }
            else {
                t[k++] = a[j++];
            }
        }
        while (i <= m)
            t[k++] = a[i++];
        while (j <= r)
            t[k++] = a[j++];
    }

    protected void copy(int[] a, int[] t, int left, int right){
        for (int i=left; i<=right; i++)
            a[i] = t[i];
    }

    public MsortAction(int[] a, int[] t, int l, int r) {
        this.a = a;
        this.t = t;
        this.l = l;
        this.r = r;
    }
}

测试代码:

import org.junit.Test;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;

public class ForkJoinTest {

    @Test
    public void MsortActionTest(){
        int size = 10000;
        int[] array = new int[size];
        Random random = new Random();

        for (int i = 0; i < size; i++) {
            array[i] = random.nextInt(1000000);
        }

        System.out.println("原数组");
        for (int i=0; i

运行试试:

果然使用了线程池就是不一样啊!0.0

内容到这就结束而,感谢观看!

你可能感兴趣的:(算法设计与分析)