《Java高并发程序设计》学习 --5.8 并行排序

45 篇文章 0 订阅
1)分离数据相关性:奇偶交换排序
对于奇偶交换排序来说,它将排序过程分为两个阶段,奇交换和偶交换。对于奇交换来说,它总是比较奇数索引以及其相邻的后续元素。而偶交换总是比较偶数索引和其相邻的后续元素。并且,奇交换和偶交换会成对出现,这样才能保证比较和交换涉及到数组中的每一个元素。
下面是奇偶交换排序的串行实现:
public static void oddEvenSort(int[] arr) {
	int exchFlag = 1, start = 0;
	while(exchFlag == 1 || start == 1) {
		exchFlag = 0;
		for(int i=start; i<arr.length-1; i+=2) {
			if(arr[i] > arr[i+1]) {
				int temp = arr[i];
				arr[i] = arr[i+1];
				arr[i+1] = temp;
				exchFlag = 1;
			}
		}
		if(start == 0)
			start = 1;
		else 
			start = 0;
	}
}
其中,exchFlag用来记录当前迭代是否发生了数据交换,而start变量用来表示是奇交换还是偶交换。初始时,start为0,表示进行偶交换,每次迭代结束后,切换start的状态。如果上一次比较交换发生了数据交换,或者当前正在进行的是奇交换,循环就不会停止,直到程序不再发生交换,并且当前进行的是偶交换为止(表示奇偶交换已经成对出现)。
改造后的并行模式代码如下:
static int arr[];
static int exchFlag = 1;
static final int NUM_ARR = 10000;
static {
	arr = new int[NUM_ARR];
	for(int i=0; i<NUM_ARR; i++) {
		arr[i] = new Random().nextInt(10000);
	}
}
static synchronized void setExchFlag(int v) {
	exchFlag = v;
}
	
static synchronized int getExchFlag() {
	return exchFlag;
}
	
public static class OddEvenSortTask implements Runnable {
	int i;
	CountDownLatch latch;
	public OddEvenSortTask(int i, CountDownLatch latch) {
		this.i = i;
		this.latch = latch;	
	}	
	@Override
	public void run() {
		if(arr[i] > arr[i+1]) {
			int temp = arr[i];
			arr[i] = arr[i+1];
			arr[i+1] = temp;
			setExchFlag(1);
		}
		latch.countDown();
	}
}	
public static void  pOddEventSort() throws InterruptedException {
	int start = 0;
	ExecutorService pool = Executors.newCachedThreadPool();
	while(getExchFlag() == 1 || start == 1) {
		setExchFlag(0);
		CountDownLatch latch = new CountDownLatch(arr.length/2 - (arr.length%2==0?start:0));
		for(int i=start; i<arr.length-1; i+=2) {
			pool.submit(new OddEvenSortTask(i, latch));
		}
		latch.await();
		if(start == 0)
			start = 1;
		else 
			start = 0;
	}
}
public static void main(String[] args) throws InterruptedException {
	pOddEventSort();
	for(int i=0; i<NUM_ARR; i++) {
		System.out.println(arr[i]);
	}
}
上述代码定义了奇偶排序的任务类。该任务的主要工作是进行数据比较和必要交换。并行排序的 主体是pOddEventSort()方法,它使用CountDownLatch记录线程数量,对于每一次迭代,使用单独的线程对每一次元素比较和交换进行操作。在下一次迭代前,必须等待上一次迭代所有线程的完成。
2)改进的插入排序:希尔排序
插入排序的基本思想是:一个未排序的数组(或链表)可以分为两个部分,前半部分是已经排序的,后半部分是未排序的。在进行排序时,只需要在未排序的部分选择一个元素,将其插入到前面有序的数组中即可。最终,未排序的部分会越来越少,直到为0,那么排序就完成了。
插入排序的实现如下:
public static void insertSort(int[] arr) {
    int length = arr.length;
    int j, i, key;
    for(int i=1; i<length; i++) {
        //key为要准备插入的元素
        key = arr[i];
        j = i - 1;
        while(j>=0 && arr[j]>key) {
            arr[j+1] = arr[j];
            j--;
        }
        //找到合适的位置插入key
        arr[j+1] = key;
    }
}


上述代码第6行,提取要准备插入的元素(也就是未排序序列中的第一个元素)。接着,在已排序队列中找到这个元素的插入位置(第8~10行),并进行插入(第13行)即可。
简单的插入排序是很难并行化的。因为这一次的 数据插入依赖于上一次得到的有序序列,因此多个步骤之间无法并行。为此,可以对插入排序进行扩展,这就是希尔排序。
希尔排序将整个数组根据间隔h分隔为若干个子数组。子数组互相穿插在一起,每一次排序时,分别对每一个子数组进行排序。
在每一次排序完成后,可以递减h的值,进行下轮更加精细的排序。直到h为1,此时等价于一次插入排序。
希尔排序的一个主要优点是,即使一个较小的元素在数组的末尾,由于每次元素移动都以h为间隔进行,因此数组末尾的小元素可以在很少的交换次数下,就被换到最接近元素最终位置的地方。
希尔排序的串行实现:
public static void shellSort(int[] arr) {
    //计算出最大的h
    int h = 1;
    while(h<=arr.length/3) {
        h = h*3+1;
    }
    while(h>0) {
        for(int i=h; i<arr.length; i++) {
            if(arr[i]<arr[i-h]) {
                int tmp = arr[i];
                int j = i - h;
                while(j>=0 && arr[j]>tmp) {
                    arr[j+h] = arr[j];
                    j-=h;
                }
                arr[j+h] = tmp;
            }
        }
        h = (h-1)+3;
    }
}
上述代码4~6行,计算一个合适的h值,接着正式进行希尔排序。第8行的for循环进行间隔为h的插入排序,每次排序结束后,递减h的值。直到h为1,退化为插入排序。
希尔排序每次都针对不同的子数组进行排序,各个子数组之间是完全独立的。因此,改写成并行程序:
public class ParallelShellSort {
	static int arr[];
	static final int ARRNUM = 1000;
	static {
		arr = new int[ARRNUM];
		for (int i = 0; i < ARRNUM; i++) {
			arr[i] = new Random().nextInt(1000);
		}
	}
	public static class ShellSortTask implements Runnable {
		int i = 0;
		int h = 0;
		CountDownLatch l;
		public ShellSortTask(int i,int h,CountDownLatch latch) {
			this.i = i;
			this.h = h;
			this.l = latch;
		}
		@Override
		public void run() {
			if(arr[i] < arr[i-h]) {
				int tmp = arr[i];
				int j = i - h;
				while(j>=0 && arr[j] > tmp) {
					arr[j+h] = arr[j];
					j -= h;
				}
				arr[j+h] = tmp;
			}
			l.countDown();
		}
	}
	public static void pShellSort() throws InterruptedException {
		int h = 1;
		CountDownLatch latch = null;
		ExecutorService pool = Executors.newCachedThreadPool();
		while(h<=arr.length/3) {
			h = h*3 + 1;
		}
		while(h>0) {
			System.out.println("h=" + h);
			if(h>=4)
				latch = new CountDownLatch(arr.length-h);
			for(int i=h; i<arr.length; i++) {
				if(h>=4) {
					pool.execute(new ShellSortTask(i, h, latch));
				} else {
					if(arr[i] < arr[i-h]) {
						int tmp = arr[i];
						int j = i -h;
						while(j>=0 && arr[j]>tmp) {
							arr[j+h] = arr[j];
							j -= h;
						}
						arr[j+h] = tmp;
					}
				}
			}
			latch.await();
			h = (h-1)/3;
		}
	}
	public static void main(String[] args) throws InterruptedException {
		pShellSort();
		for(int i=0; i<ARRNUM; i++) {
			System.out.println(arr[i]);
		}
	}
}
上述代码中定义ShellSortTask作为并行任务。一个ShellSortTask的作用是根据给定的起始位置和h,对子数组进行排序,因此可以完全并行化。
为控制线程数量,这里定义并行主函数pShellSort()在h大于或等于4时使用并行线程,否则则退化为传统的插入排序。
每次计算后,递减h的值。


注:本篇博客内容摘自《 Java 高并发程序设计》
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值