多線程處理歸併排序的方法通常爲:java
假設有n個線程同步處理,就將數組等分紅n份,每一個線程處理一份,再對最後n個有序數組進行歸併。算法
爲了使對整個算法具備可擴展性,即線程數n能夠自定義,筆者將線程類、處理數組類等進行封裝,分爲最主要的4個類:Array, Merge, MyThread, MoreThreads
,代碼以下:數組
/*Array.java*/ import java.util.ArrayList; /** * @author duyue * * 這個類用來處理數組 * * 原理: * 建立待排序數組成功後,須要配合多線程(假設有n個線程)分別排序, * 須要將數組儘可能等分紅n個分數組(保存到列表中),由n個線程分別歸 * 並排序,並將各個有序數組(再次保存到列表中),最後整合(不歸併 * 整合)並覆蓋原數組,等待最後歸併。 */ class Array { /** * 構造一個保存數組的列表,用於保存分割後的分數組 */ static ArrayList<int[]> arrayList = new ArrayList<int[]>(); /** * @param length 數組長度 * @return 待排序的數組 */ static int[] createArray(int length) { int[] array = new int[length]; for (int i = 0; i < length; i++) { array[i] = (int) (Math.random() * 10000); } return array; } /** * @param array 待分割(多線程排序須要)的數組 * @param num 線程數,即要分割的份數 */ static void divideArray(int[] array, int num) { int k = 0; //記錄原數組的複製進度,k表明當前數組的複製初始點 for (int i = 0; i < num; i++) { int point = array.length / num; //分數組的長度 int[] a = new int[0]; //保存分數組 /*考慮到不夠整除的狀況,將剩餘的項所有放在最後一個分數組中*/ if (i != num - 1) a = new int[point]; if (i == num - 1) a = new int[array.length - k]; /*將array[k, k + a.length -1]複製到a[0, a.length]中*/ System.arraycopy(array, k, a, 0, a.length); arrayList.add(a); //把獲得的分數組保存在列表中 k += point; //移動複製初始點 } } /** * @param newArray 由有序分數組整合(不歸併)的新數組 * @param num 有序分數組的個數,即由num個線程分別排序後獲得的數組數,也就是線程數 */ static void newArray(int[] newArray, int num) { /*原理與divideArray方法類似*/ int k = 0; //記錄新數組的待整合初始點 /*把列表元素(即數組)逐個複製到新的數組中*/ for (int i = 0; i < num; i++) { System.arraycopy(arrayList.get(i), 0, newArray, k, arrayList.get(i).length); k += arrayList.get(i).length; //移動待整合初始點 } } }
/*Merge.java*/ /** * @author duyue * * 這是對數組進行歸併排序的類 */ class Merge { private int[] temp; //暫時存放待排序數組的temp數組 /** * @param a 待排序的數組由構造器傳遞到類中 */ Merge(int[] a) { temp = new int[a.length]; } public void sort(int[] a) { sort(a, 0, a.length - 1); } public void sort(int[] a, int low, int high) { if (low >= high) return; int mid = low + (high - low) / 2; sort(a, low, mid); //將左半邊排序 sort(a, mid + 1, high); //將左半邊排序 merge(a, low, mid, high); //歸併結果 } /** * @param a 待歸併的數組,其中a[low,mid]和a[mid+1,high]都是有序的 */ public void merge(int[] a, int low, int mid, int high) { int i = low, j = mid + 1; /*將a[low,high]複製到temp[low,high]*/ System.arraycopy(a, low, temp, low, high - low + 1); /*歸併到a[low,high]*/ for (int k = low; k <= high; k++) { if (i > mid) a[k] = temp[j++]; else if (j > high) a[k] = temp[i++]; else if (temp[j] < temp[i]) a[k] = temp[j++]; else a[k] = temp[i++]; } } }
/*MyThread.java*/ import java.util.concurrent.CountDownLatch; /** * @author duyue * * 這個類用來定義線程,使其可以對數組進行歸併排序處理 */ class MyThread extends Thread { public int[] aux; //定義一個數組,用來保存待處理的數組 private CountDownLatch latch; //定義這個類用來等待各個線程都完成工做,再進行下一步操做 /*經過構造器將待處理的數組傳遞到線程的類中*/ public MyThread(int[] aux, CountDownLatch latch) { this.aux = aux; this.latch = latch; } public void run() { Merge mergeThread = new Merge(aux); mergeThread.sort(aux); latch.countDown(); } }
/*MoreThreads.java*/ import java.util.ArrayList; import java.util.concurrent.CountDownLatch; /** * @author duyue * * 本類是多線程處理歸併排序的核心部分。 * * 原理: * 由用戶指定線程數,例如n個線程,將數組分爲n份,分別用n個線程對這n個數組進行歸併排序, * 獲得n個有序分數組,再對這n個有序數組歸併就得出最後的結果。 * 線程數越多,相應的速度就會越快。 * 要處理的數組長度越長,多線程與單線程的對比就越大。 */ class MoreThreads { /** * @param num 線程數,由用戶定義 */ MoreThreads(int num) { System.out.println("如今是" + num + "個線程處理歸併排序:"); int length = 100; //數組總長度 for (int j = 0; j < 6; j++) { /*記錄起始時間*/ long beginTime = System.currentTimeMillis(); /*建立待排序的數組*/ int[] myArray = Array.createArray(length); /*將數組近乎等分紅num份,以便利用多線程對各個數組排序*/ Array.divideArray(myArray, num); /* * 對各個數組利用num個線程同步排序。 * 將num個線程保存在列表threads中,方便將各個線程處理後的數組調出。 * CountDownLatch類用於等待全部的線程都工做完成後,進行最終的歸併。 */ ArrayList<MyThread> threads = new ArrayList<MyThread>(); CountDownLatch latch = new CountDownLatch(num); for (int i = 0; i < num; i++) { MyThread thread = new MyThread(Array.arrayList.get(i), latch); thread.start(); threads.add(thread); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } /* * 清空原裝有數組列表中的元素, * 將排序後的各個數組從threads列表中調出,添加到數組列表Array中 */ Array.arrayList.clear(); for (int i = 0; i < num; i++) { Array.arrayList.add(threads.get(i).aux); } /*把各個排序後數組規整到長數組中,並對三個有序數組歸併到一個數組中*/ Array.newArray(myArray, num); /* * 對有序數組進行歸併 * 歸併原理: * 將第一個有序分數組(即第一個線程排序後的數組)與其下一個有序分數組(即第二個線程 * 排序後的數組)歸併成一個數組,再把歸併的數組與其下一個有序分數組(即第三個線程排 * 序後的數組)歸併,依此類推. */ int low = 0; //整合後的長數組myArray的首位 int mid = -1; //待歸併的第一個有序分數組的末位 int high; //待歸併的第二個有序分數組的末位 for (int i = 0; i < num - 1; i++) { Merge merge = new Merge(myArray); mid = Array.arrayList.get(i).length + mid; high = mid + Array.arrayList.get(i + 1).length; merge.merge(myArray, low, mid, high); } /*記錄結束時間*/ long endTime = System.currentTimeMillis(); System.out.println(length + "項數組歸併排序的時間:" + (endTime - beginTime) + "ms"); length = length * 10; Array.arrayList.clear(); //清空列表內容,對下一次循環不形成影響 } } }
運行如下代碼便可測試:多線程
/*TestThread*/ import java.util.Scanner; /** * @author duyue * * 這是一個測試類,用於展現結果。 */ public class TestThread { public static void main(String[] args) { new MoreThreads(1); System.out.println("--------------------------------"); new MoreThreads(2); System.out.println("--------------------------------"); new MoreThreads(3); System.out.println("--------------------------------"); System.out.println("你還想嘗試更多線程處理歸併排序嗎?(y:yes, n:no)"); while (true) { Scanner in = new Scanner(System.in); String s = in.nextLine(); if (s.equals("n")) { System.out.println("byebye!"); in.close(); break; } else if (s.equals("y")) { System.out.println("請輸入要嘗試的線程數:"); new MoreThreads(in.nextInt()); System.out.println("--------------------------------"); System.out.println("你還想嘗試更多線程處理歸併排序嗎?(y:yes, n:no)"); } else System.out.println("輸入錯誤!請從新輸入"); } } }