1、先回顾一下冒泡排序
public class BubbleSort {
public static void bubbleSort(int[] arr) {
for (int i = arr.length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
}
System.out.println(Arrays.toString(arr));
}
}
}
public static void main(String[] args) {
int[] arr = new int[] { 5, 4, 3, 2, 1 };
bubbleSort(arr);
}
}
[4, 5, 3, 2, 1]
[4, 3, 5, 2, 1]
[4, 3, 2, 5, 1]
[4, 3, 2, 1, 5]
[3, 4, 2, 1, 5]
[3, 2, 4, 1, 5]
[3, 2, 1, 4, 5]
[2, 3, 1, 4, 5]
[2, 1, 3, 4, 5]
[1, 2, 3, 4, 5]
在冒泡排序的每次迭代交换过程中,每次交换的两个元素存在数据冲突,对于每个元素,都有可能与其前面或后面的元素交换,因此很难改成并行算法;
2、奇偶交换排序
需要解开数据的相关性,就容易使用并行算法来实现排序;
分为奇交换和偶交换,奇交换比较奇数索引及其相邻后续元素,偶交换比较偶数索引及其相邻后续元素;奇偶校验成对出现;
奇偶交换迭代示意图如下
2.1 奇偶校验的串行实现如下
public class OddEvenSort {
public static void oddEvenSort(int[] arr) {
// 记录当前迭代是否发生数据交换,1为发生交换,0为未发生
int exchangeFlag = 1;
// 表示当前是奇交换还是偶交换,0为偶交换,1为奇交换
int start = 0;
// 如果上一次迭代发生了数据交换,或者当前正在进行的是奇交换,循环不会停止
// 直到程序不在发生交换,并且当前进行的是偶交换
while (exchangeFlag == 1 || start == 1) {
exchangeFlag = 0;
for (int i = start; i < arr.length - 1; i += 2) {
if (arr[i] > arr[i + 1]) {
int tmp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = tmp;
exchangeFlag = 1;
}
System.out.println(Arrays.toString(arr));
}
// 每次迭代结束,切换奇偶交换状态
if (start == 0) {
start = 1;
} else {
start = 0;
}
}
}
public static void main(String[] args) {
int[] arr = new int[] { 5, 4, 3, 2, 1 };
oddEvenSort(arr);
}
}
2.2 奇偶校验的并行模式实现
上述代码中可以看到,由于把整个比较交换独立分割为奇校验和偶校验,使得在每个阶段内,所有的比较和交换都是没有数据相关性的,所以可以比较方便的改造为并行模式:
public class ParallelOddEvenSort {
static volatile int[] arr;
static int exchangeFlag = 1;
static ExecutorService pool = Executors.newCachedThreadPool();
static synchronized void setExchangeFlag(int v) {
exchangeFlag = v;
}
static synchronized int getExchangeFlag() {
return exchangeFlag;
}
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 tmp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = tmp;
setExchangeFlag(1);
}
System.out.println(Arrays.toString(arr));
latch.countDown();
}
}
public static void pOddEvenSort(int[] arr) throws InterruptedException {
int start = 0;
while (getExchangeFlag() == 1 || start == 1) {
setExchangeFlag(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 {
arr = new int[] { 5, 4, 3, 2, 1 };
pOddEvenSort(arr);
pool.shutdown();
}
}
[4, 5, 2, 3, 1]
[4, 5, 2, 3, 1]
[4, 5, 2, 1, 3]
[4, 2, 5, 1, 3]
[4, 2, 1, 5, 3]
[2, 4, 1, 5, 3]
[2, 1, 4, 3, 5]
[2, 1, 4, 3, 5]
[1, 2, 4, 3, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
3、插入排序
3.1 普通的插入排序
一个未排序的的数组分为两部分,前半部分为已经排序的,后半部分为未排序的;排序时,从未排序的后半部分中选择一个元素,将其插入前面有序的数组中即可,如此循环往复,直到未排序的部分为0即完成排序。
普通插入排序因为上一次的数据插入依赖于上一次得到的有序序列,所以多个步骤无法并行。
public class InsertSort {
public static void insertSort(int[] arr) {
int length = arr.length;
int i, j, key;
for (i = 1; i < length; i++) {
key = arr[i];
j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
System.out.println(Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = new int[] { 5, 4, 3, 2, 1 };
insertSort(arr);
}
}
[4, 5, 3, 2, 1]
[3, 4, 5, 2, 1]
[2, 3, 4, 5, 1]
[1, 2, 3, 4, 5]
3.2 希尔排序 Shell's Sort
希尔排序是插入排序的扩展,将整个数组根据间隔n分割为若干个子数组(即数组序号为n+1的倍数的元素为一组),每次排序,分别对每一个子数组进行插入排序;每组排序完成后,递减n值,进行更加精细的排序,直到n为1时,此时等价于一次插入排序;
其串行实现如下:(假设n=3)
public class ShellSort {
public static void shellSort(int[] arr) {
// 计算合适的间隔
int n = 1;
while (n <= arr.length / 3) {
n = n * 3 + 1;
}
while (n > 0) {
for (int i = n; i < arr.length; i++) {
if (arr[i] < arr[i - n]) {
int tmp = arr[i];
int j = i - n;
while (j >= 0 && arr[j] > tmp) {
arr[j + n] = arr[j];
j -= n;
}
arr[j + n] = tmp;
}
}
// 递减间隔值
n = (n - 1) / 3;
System.out.println(Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = new int[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
shellSort(arr);
}
}
[1, 4, 3, 2, 5, 8, 7, 6, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
即使一个较小的元素在数组的末尾,由于每次元素移动都以n为间隔进行,因此数组末尾的小元素可以在很少的交换次数下被置换到最接近元素最终位置的地方;
并行版
public class ParallelShellSort {
static volatile int[] arr;
static ExecutorService pool = Executors.newCachedThreadPool();
public static class ShellSortTask implements Runnable {
int i = 0;
int n = 0;
CountDownLatch latch;
public ShellSortTask(int i, int n, CountDownLatch latch) {
this.i = i;
this.n = n;
this.latch = latch;
}
@Override
public void run() {
if (arr[i] < arr[i - n]) {
int tmp = arr[i];
int j = i - n;
while (j >= 0 && arr[j] > tmp) {
arr[j + n] = arr[j];
j -= n;
}
arr[j + n] = tmp;
}
latch.countDown();
}
}
public static void pShellSort(int[] arr) throws InterruptedException {
int n = 1;
CountDownLatch latch = null;
while (n <= arr.length / 3) {
n = n * 3 + 1;
}
while (n > 0) {
System.out.println("n=" + n);
if (n >= 4) {
latch = new CountDownLatch(arr.length - n);
}
for (int i = n; i < arr.length; i++) {
if (n >= 4) {
pool.execute(new ShellSortTask(i, n, latch));
} else {
if (arr[i] < arr[i - n]) {
int tmp = arr[i];
int j = i - n;
while (j >= 0 && arr[j] > tmp) {
arr[j + n] = arr[j];
j -= n;
}
arr[j + n] = tmp;
}
System.out.println(Arrays.toString(arr));
}
}
// 等待线程排序完成进入下一次排序
latch.await();
n = (n - 1) / 3;
}
}
public static void main(String[] args) throws InterruptedException {
arr = new int[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
pShellSort(arr);
pool.shutdown();
}
}
n=4
n=1
[1, 4, 3, 2, 5, 8, 7, 6, 9]
[1, 3, 4, 2, 5, 8, 7, 6, 9]
[1, 2, 3, 4, 5, 8, 7, 6, 9]
[1, 2, 3, 4, 5, 8, 7, 6, 9]
[1, 2, 3, 4, 5, 8, 7, 6, 9]
[1, 2, 3, 4, 5, 7, 8, 6, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]