並行排序

排序是一項很是經常使用的操做,你的應用程序在運行時,可能無時無刻不在進行排序操做。排序的算法有不少,可是對於大部分的算法都是串行執行的。當排序的元素不少時,若使用並行算法代替串行,顯然能夠更加有效的利用CPU,提升排序效率。但將串行算法修改成並行算法並不是易事,甚至會極大的增長原有算法的複雜度。下面介紹幾種相對簡單的算法。算法

奇偶交換排序:分離數據相關性

  在介紹奇偶交換排序前,首先來看一下冒泡排序。在這裏,咱們將數組從小到大排序:shell

 1 public static int[] bubbleSort(int[] arr){
 2         for (int i = 0;i < arr.length - 1;i++){
 3             for (int j = 0;j < arr.length - 1 - i;j++){
 4                 if (j+1 == arr.length-i){
 5                     break;
 6                 }
 7                 if (arr[j] > arr[j+1]){
 8                     int temp = arr[j];
 9                     arr[j] = arr[j+1];
10                     arr[j+1] = temp;
11                 }
12             }
13         }
14         return arr;
15     }

  在冒泡排序的交換過程當中,因爲每次交換的兩個元素存在數據衝突,也就是對於每一個元素,它既可能與前面的元素交換,也可能與後面的元素交換,所以很難直接改形成並行算法。若是可以解開這種數據的相關性,就能夠比較容易的使用並行算法來實現相似的排序。奇偶交換排序就是基於這種思想的對於奇偶交換來講,它將排序過程分爲兩個階段,奇交換和偶交換。對於奇交換來講,它老是比較奇數索引以及相鄰的後續元素;偶交換老是比較偶數索引和其相鄰的後續元素,而且奇交換和偶交換會成對出現,這樣才能保證比較和交換涉及到數組中的每個元素。數組

 從上圖能夠看出,因爲將整個比較交換獨立分割爲奇階段和偶階段。這就使得在每個階段內,全部的比較和交換都是沒有數據相關性的。所以,每一次比較和交換均可以獨立執行,也就能夠並行化了。併發

下面是奇偶交換的串行實現:ide

 1 public static int[] oddEvevSort(int[] arr){
 2         int exchFlag = 1,start = 0;
 3         while (exchFlag == 1 || start == 1){
 4             exchFlag = 0;
 5             for (int i = start;i < arr.length - 1;i += 2){
 6                 if (arr[i] > arr[i+1]){
 7                     int temp = arr[i];
 8                     arr[i] = arr[i+1];
 9                     arr[i+1] = temp;
10                     exchFlag = 1;
11                 }
12             }
13             if (start == 0){
14                 start = 1;
15             }else {
16                 start = 0;
17             }
18         }
19         return arr;
20     }

上述代碼中,exchFlag用來記錄當前迭代是否發生了數據交換,而start變量用來表示是奇交換仍是偶交換。初始時,start爲0,表示進行偶交換,每次迭代結束後,切換start狀態。若是上一次發生了數據交換,或者當前進行的是奇交換,循環就不會中止,直到程序再也不發生交換,而且當前進行的是偶交換爲止(表示奇偶交換已經成對出現)。高併發

下面是奇偶交換的並行排序:測試

 1 public class CurrentOddEven {
 2     static int[] arr = {23,3,23,4,5,5,6,6,6453,678,68,9,9,79878,97,897897,98,78};
 3     static int exchFlag = 1;
 4     static synchronized void setExchFlag(int v){
 5         exchFlag = v;
 6     }
 7 
 8     static synchronized int getExchFlag(){
 9         return exchFlag;
10     }
11     public static class OddEvenSortTask implements Runnable{
12         int i;
13         CountDownLatch latch;
14 
15         public OddEvenSortTask(int i,CountDownLatch latch){
16             this.i = i;
17             this.latch = latch;
18         }
19 
20         @Override
21         public void run() {
22             if (arr[i] > arr[i+1]){
23                 int temp = arr[i];
24                 arr[i] = arr[i+1];
25                 arr[i+1] = temp;
26                 setExchFlag(1);
27             }
28             latch.countDown();
29         }
30     }
31 
32     public static void main(String[] args) throws InterruptedException {
33         int start = 0;
34         ExecutorService es = Executors.newCachedThreadPool();
35         while (getExchFlag() == 1 || start == 1){
36             setExchFlag(0);
37             //偶數的數組長度,當start=1時,只有length/2-1 個線程
38             CountDownLatch latch = new CountDownLatch(arr.length/2-(arr.length%2 == 0?start:0));
39             for (int i = start;i < arr.length - 1;i += 2){
40                 es.submit(new OddEvenSortTask(i,latch));
41             }
42             //等待全部線程結束
43             latch.await();
44             if (start == 0){
45                 start = 1;
46             }else {
47                 start = 0;
48             }
49         }
50         for (int temp:arr){
51             System.out.println(temp);
52         }
53         es.shutdown();
54     }
55 }

 上述代碼第11行,定義了奇偶排序的任務類。該任務的主要工做是進行數據的比較和交換(第22~26行)。並行排序的主體在main方法中,使用了countDownLatch來記錄線程數量,對於每一次的迭代,使用單獨的線程對每一次元素比較和交換操做,在下一次的迭代開始以前,必須等上一次的迭代必須完成。this

 希爾排序:改進的插入排序

插入排序也是一種很常見的排序算法。它的基本思想:一個未排序的數組(固然也能夠是鏈表)能夠分爲兩個部分,前半部分是已經排序的,後半部分是未排序的。在進行排序時,只須要在未排序的部分中選擇一個元素,將其插入前面有序的數組中便可。最終,未排序的部分會愈來愈少,直到爲0,那麼排序就完成了。初始時,能夠假設已排序部分就是第一個元素。spa

插入排序串行排序以下:線程

 1 public static int[] insertSort(int[] arr){
 2         int length = arr.length;
 3         int i,j,key;
 4         for (i = 1;i < length;i++){
 5             //key爲要準備插入的元素
 6             key = arr[i];
 7             j = i - 1;
 8             while (j >= 0 && arr[j] > key){
 9                 arr[j+1] = arr[j];
10                 j--;
11             }
12             //找到合適的位置插入key
13             arr[j+1] = key;
14         }
15         return arr;
16     }

上述代碼第6行,提取準備插入的元素(也就是未排序序列中的第一個元素)。接着,在已排序的序列中找到這個元素的插入位置(第8~10行),並進行插入(第13行)便可。

簡單的插入排序是很難進行並行化的。由於這一次的數據插入依賴於上一次獲得的有序序列,所以多個步驟之間沒法並行。爲此,咱們能夠對插入排序進行擴展,就是希爾排序。

希爾排序將整個數組根據間隔h分割爲若干個數組。子數組相互穿插在一塊兒,每一次排序時,分別對每個子數組進行排序。以下圖所示(借大神的圖侵刪):

從上圖能夠看出,每一組排序完成後,能夠遞減h的值,進行下輪更精細的排序,知道h=1,此時,等價於一次插入排序。

希爾排序的一個主要優勢是,即便最小的一個元素在數組的末尾,因爲每次元素的移動都以h爲間隔進行,所以數組末尾的小元素能夠在不多的交換次數下,就被置換到最接近元素最終位置的地方。

下面是希爾排序的串行實現:

 1 public static int[] shellSort(int[] arr){
 2         //計算出最大的h值
 3         int h = 1;
 4         while (h <= arr.length/3){
 5             h = h*3 + 1;
 6         }
 7         while (h > 0){
 8             for (int i = h;i < arr.length;i++){
 9                 if (arr[i] < arr[i - h]){
10                     int temp = arr[i];
11                     int j = i - h;
12                     while (j >= 0 && arr[j] > temp){
13                         arr[j + h] = arr[j];
14                         j -= h;
15                     }
16                     arr[j + h] = temp;
17                 }
18             }
19             //計算出下一個h值
20             h = (h - 1)/3;
21         }
22         return arr;
23     }

上述代碼的4~6行,計算出一個合適的h值,接着進行正式的希爾排序。第8行代碼的for循環進行間隔爲h的插入排序,每次排序結束後,遞減h的值(第20行),直到h=1,退化爲插入排序。

因爲希爾排序每次都針對不一樣的子數組進行排序,各個子數組之間是徹底獨立的,所以,是能夠改寫爲並行程序的:

 1 public class CurrentShellSort {
 2     static int[] a = {2,3,1,45,53,3,55,4,65,765,7,7,89687,6,89,69,4354,89,99};
 3     static ExecutorService pool = Executors.newCachedThreadPool();
 4 
 5     public static class ShellSortTask implements Runnable {
 6 
 7         int x = 0;
 8         int h = 0;
 9         CountDownLatch l;
10 
11         public ShellSortTask(int x, int h, CountDownLatch latch) {
12             this.x = x;
13             this.h = h;
14             this.l = latch;
15         }
16 
17         public void run() {
18             int i, j, key;
19             for (i = x + h; i < a.length; i = i + h) {
20                 if (a[i] < a[i - h]) {
21                     j = i - h;
22                     key = a[i];
23                     while (j >= 0 && a[j] > key) {
24                         a[j + h] = a[j];
25                         j -= h;
26                     }
27                     a[j + h] = key;
28                 }
29             }
30             l.countDown();
31         }
32     }
33 
34     public static void pShellSort(int[] arr) throws InterruptedException {
35         // 計算出最大的n值
36         int h = 1;
37         CountDownLatch lathc = null;
38         while (h <= arr.length / 3) {
39             h = h * 3 + 1;
40         }
41         while (h > 0) {
42             System.out.println("h=" + h);
43             lathc = new CountDownLatch(h);
44             for (int x = 0; x < h; x++) {
45                 pool.submit(new ShellSortTask(x, h, lathc));
46             }
47             lathc.await();
48             System.out.println(Arrays.toString(arr));
49             // 計算下一個h值
50             h = (h - 1) / 3;
51         }
52         pool.shutdown();
53     }
54     //測試
55     public static void main(String[] args) throws InterruptedException {
56         pShellSort(a);
57     }
58 }

 輸出結果:

h=13
[2, 3, 1, 45, 53, 3, 55, 4, 65, 765, 7, 7, 89687, 6, 89, 69, 4354, 89, 99]
h=4
[2, 3, 1, 4, 53, 3, 7, 7, 65, 6, 55, 45, 4354, 89, 89, 69, 89687, 765, 99]
h=1
[1, 2, 3, 3, 4, 6, 7, 7, 45, 53, 55, 65, 69, 89, 89, 99, 765, 4354, 89687]

 

參考:《Java高併發程序設計》 葛一鳴 郭超 編著:

相關文章
相關標籤/搜索