多线程处理归并排序的方法通常为: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("输入错误!请从新输入"); } } }